class ActiveMerchant::Billing::FirstdataE4V27Gateway

Constants

BRANDS
DEFAULT_ECI
SENSITIVE_FIELDS
STANDARD_ERROR_CODE_MAPPING
SUCCESS
TRANSACTIONS

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 58
def initialize(options = {})
  requires!(options, :login, :password, :key_id, :hmac_key)
  @options = options

  super
end

Public Instance Methods

authorize(money, credit_card_or_store_authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 65
def authorize(money, credit_card_or_store_authorization, options = {})
  commit(:authorization, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options))
end
capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 73
def capture(money, authorization, options = {})
  commit(:capture, build_capture_or_credit_request(money, authorization, options))
end
purchase(money, credit_card_or_store_authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 69
def purchase(money, credit_card_or_store_authorization, options = {})
  commit(:sale, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options))
end
refund(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 81
def refund(money, authorization, options = {})
  commit(:credit, build_capture_or_credit_request(money, authorization, options))
end
scrub(transcript) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 114
def scrub(transcript)
  transcript.
    gsub(%r((<Card_Number>).+(</Card_Number>)), '\1[FILTERED]\2').
    gsub(%r((<CVDCode>).+(</CVDCode>)), '\1[FILTERED]\2').
    gsub(%r((<Password>).+(</Password>))i, '\1[FILTERED]\2').
    gsub(%r((<CAVV>).+(</CAVV>)), '\1[FILTERED]\2').
    gsub(%r((CARD NUMBER\s+: )#+\d+), '\1[FILTERED]')
end
store(credit_card, options = {}) click to toggle source

Tokenize a credit card with TransArmor

The TransArmor token and other card data necessary for subsequent transactions is stored in the response's authorization attribute. The authorization string may be passed to authorize and purchase instead of a ActiveMerchant::Billing::CreditCard instance.

TransArmor support must be explicitly activated on your gateway account by FirstData. If your authorization string is empty, contact FirstData support for account setup assistance.

support.payeezy.com/hc/en-us/articles/203731189-TransArmor-Tokenization

# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 101
def store(credit_card, options = {})
  commit(:store, build_store_request(credit_card, options), credit_card)
end
supports_network_tokenization?() click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 123
def supports_network_tokenization?
  true
end
supports_scrubbing?() click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 110
def supports_scrubbing?
  true
end
verify(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 85
def verify(credit_card, options = {})
  commit(:verify, build_sale_or_authorization_request(0, credit_card, options))
end
verify_credentials() click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 105
def verify_credentials
  response = void('0')
  response.message != 'Unauthorized Request. Bad or missing credentials.'
end
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 77
def void(authorization, options = {})
  commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options))
end

Private Instance Methods

add_address(xml, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 288
def add_address(xml, options)
  if (address = options[:billing_address] || options[:address])
    xml.tag! 'Address' do
      xml.tag! 'Address1', address[:address1]
      xml.tag! 'Address2', address[:address2] if address[:address2]
      xml.tag! 'City', address[:city]
      xml.tag! 'State', address[:state]
      xml.tag! 'Zip', address[:zip]
      xml.tag! 'CountryCode', address[:country]
    end
    xml.tag! 'ZipCode', address[:zip]
  end
end
add_amount(xml, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 200
def add_amount(xml, money, options)
  currency_code = options[:currency] || default_currency
  xml.tag! 'DollarAmount', localized_amount(money, currency_code)
  xml.tag! 'Currency', currency_code
end
add_card_authentication_data(xml, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 260
def add_card_authentication_data(xml, options)
  xml.tag! 'CAVV', options[:cavv]
  xml.tag! 'XID', options[:xid]
end
add_credentials(xml) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 184
def add_credentials(xml)
  xml.tag! 'ExactID', @options[:login]
  xml.tag! 'Password', @options[:password]
end
add_credit_card(xml, credit_card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 206
def add_credit_card(xml, credit_card, options)
  if credit_card.respond_to?(:track_data) && credit_card.track_data.present?
    xml.tag! 'Track1', credit_card.track_data
    xml.tag! 'Ecommerce_Flag', 'R'
  else
    xml.tag! 'Card_Number', credit_card.number
    xml.tag! 'Expiry_Date', expdate(credit_card)
    xml.tag! 'CardHoldersName', credit_card.name
    xml.tag! 'CardType', card_type(credit_card.brand)
    xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id]

    add_credit_card_eci(xml, credit_card, options)
    add_credit_card_verification_strings(xml, credit_card, options)
  end
end
add_credit_card_eci(xml, credit_card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 222
def add_credit_card_eci(xml, credit_card, options)
  eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover'
          # Discover requires any Apple Pay transaction, regardless of in-app
          # or web, and regardless of the ECI contained in the PKPaymentToken,
          # to have an ECI value explicitly of 04.
          '04'
        else
          (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
        end

  xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci
end
add_credit_card_token(xml, store_authorization, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 265
def add_credit_card_token(xml, store_authorization, options)
  params = store_authorization.split(';')
  credit_card = CreditCard.new(
    :brand      => params[1],
    :first_name => params[2],
    :last_name  => params[3],
    :month      => params[4],
    :year       => params[5])

  xml.tag! 'TransarmorToken', params[0]
  xml.tag! 'Expiry_Date', expdate(credit_card)
  xml.tag! 'CardHoldersName', credit_card.name
  xml.tag! 'CardType', card_type(credit_card.brand)
  xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id]
  add_card_authentication_data(xml, options)
end
add_credit_card_verification_strings(xml, credit_card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 235
def add_credit_card_verification_strings(xml, credit_card, options)
  if credit_card.is_a?(NetworkTokenizationCreditCard)
    add_network_tokenization_credit_card(xml, credit_card)
  else
    if credit_card.verification_value?
      xml.tag! 'CVD_Presence_Ind', '1'
      xml.tag! 'CVDCode', credit_card.verification_value
    end

    add_card_authentication_data(xml, options)
  end
end
add_customer_data(xml, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 282
def add_customer_data(xml, options)
  xml.tag! 'Customer_Ref', options[:customer] if options[:customer]
  xml.tag! 'Client_IP', options[:ip] if options[:ip]
  xml.tag! 'Client_Email', options[:email] if options[:email]
end
add_identification(xml, identification) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 193
def add_identification(xml, identification)
  authorization_num, transaction_tag, _ = identification.split(';')

  xml.tag! 'Authorization_Num', authorization_num
  xml.tag! 'Transaction_Tag', transaction_tag
end
add_invoice(xml, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 302
def add_invoice(xml, options)
  xml.tag! 'Reference_No', options[:order_id]
  xml.tag! 'Reference_3',  options[:description] if options[:description]
end
add_level_3(xml, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 312
def add_level_3(xml, options)
  xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3]
end
add_network_tokenization_credit_card(xml, credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 248
def add_network_tokenization_credit_card(xml, credit_card)
  case card_brand(credit_card).to_sym
  when :american_express
    cryptogram = Base64.decode64(credit_card.payment_cryptogram)
    xml.tag!('XID', Base64.encode64(cryptogram[20...40]))
    xml.tag!('CAVV', Base64.encode64(cryptogram[0...20]))
  else
    xml.tag!('XID', credit_card.transaction_id) if credit_card.transaction_id
    xml.tag!('CAVV', credit_card.payment_cryptogram)
  end
end
add_stored_credentials(xml, card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 316
def add_stored_credentials(xml, card, options)
  return unless options[:stored_credential]
  xml.tag! 'StoredCredentials' do
    xml.tag! 'Indicator', stored_credential_indicator(xml, card, options)
    if initiator = options.dig(:stored_credential, :initiator)
      xml.tag! initiator == 'merchant' ? 'M' : 'C'
    end
    if reason_type = options.dig(:stored_credential, :reason_type)
      xml.tag! 'Schedule', reason_type == 'unscheduled' ? 'U' : 'S'
    end
    xml.tag! 'AuthorizationTypeOverride', options[:authorization_type_override] if options[:authorization_type_override]
    if network_transaction_id = options[:stored_credential][:network_transaction_id]
      xml.tag! 'TransactionId', network_transaction_id
    else
      xml.tag! 'TransactionId', 'new'
    end
    xml.tag! 'OriginalAmount', options[:original_amount] if options[:original_amount]
    xml.tag! 'ProtectbuyIndicator', options[:protectbuy_indicator] if options[:protectbuy_indicator]
  end
end
add_tax_fields(xml, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 307
def add_tax_fields(xml, options)
  xml.tag! 'Tax1Amount',  options[:tax1_amount] if options[:tax1_amount]
  xml.tag! 'Tax1Number',  options[:tax1_number] if options[:tax1_number]
end
add_transaction_type(xml, action) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 189
def add_transaction_type(xml, action)
  xml.tag! 'Transaction_Type', TRANSACTIONS[action]
end
authorization_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 400
def authorization_from(response)
  if response[:authorization_num] && response[:transaction_tag]
    [
      response[:authorization_num],
      response[:transaction_tag],
      (response[:dollar_amount].to_f * 100).round
    ].join(';')
  else
    ''
  end
end
build_capture_or_credit_request(money, identification, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 163
def build_capture_or_credit_request(money, identification, options)
  xml = Builder::XmlMarkup.new

  add_identification(xml, identification)
  add_amount(xml, money, options)
  add_customer_data(xml, options)
  add_card_authentication_data(xml, options)

  xml.target!
end
build_request(action, body) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 129
def build_request(action, body)
  xml = Builder::XmlMarkup.new

  xml.instruct!
  xml.tag! 'Transaction', xmlns: 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes' do
    add_credentials(xml)
    add_transaction_type(xml, action)
    xml << body
  end

  xml.target!
end
build_sale_or_authorization_request(money, credit_card_or_store_authorization, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 142
def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)
  xml = Builder::XmlMarkup.new

  add_amount(xml, money, options)

  if credit_card_or_store_authorization.is_a? String
    add_credit_card_token(xml, credit_card_or_store_authorization, options)
  else
    add_credit_card(xml, credit_card_or_store_authorization, options)
    add_stored_credentials(xml, credit_card_or_store_authorization, options)
  end

  add_address(xml, options)
  add_customer_data(xml, options)
  add_invoice(xml, options)
  add_tax_fields(xml, options)
  add_level_3(xml, options)

  xml.target!
end
build_store_request(credit_card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 174
def build_store_request(credit_card, options)
  xml = Builder::XmlMarkup.new

  add_credit_card(xml, credit_card, options)
  add_address(xml, options)
  add_customer_data(xml, options)

  xml.target!
end
card_type(credit_card_brand) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 349
def card_type(credit_card_brand)
  BRANDS[credit_card_brand.to_sym] if credit_card_brand
end
commit(action, data, credit_card = nil) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 353
def commit(action, data, credit_card = nil)
  url = (test? ? self.test_url : self.live_url)
  request = build_request(action, data)
  begin
    response = parse(ssl_post(url, request, headers('POST', url, request)))
  rescue ResponseError => e
    response = parse_error(e.response)
  end

  Response.new(successful?(response), message_from(response), response,
    :test => test?,
    :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '',
    :avs_result => {:code => response[:avs]},
    :cvv_result => response[:cvv2],
    :error_code => standard_error_code(response)
  )
end
expdate(credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 345
def expdate(credit_card)
  "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
headers(method, url, request) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 371
def headers(method, url, request)
  content_type = 'application/xml'
  content_digest = Digest::SHA1.hexdigest(request)
  sending_time = Time.now.utc.iso8601
  payload = [method, content_type, content_digest, sending_time, url.split('.com')[1]].join("\n")
  hmac = OpenSSL::HMAC.digest('sha1', @options[:hmac_key], payload)
  encoded = Base64.strict_encode64(hmac)

  {
    'x-gge4-date' => sending_time,
    'x-gge4-content-sha1' => content_digest,
    'Authorization' => 'GGE4_API ' + @options[:key_id].to_s + ':' + encoded,
    'Accepts' => content_type,
    'Content-Type' => content_type
  }
end
message_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 432
def message_from(response)
  if response[:faultcode] && response[:faultstring]
    response[:faultstring]
  elsif response[:error_number] && response[:error_number] != '0'
    response[:error_description]
  else
    result = (response[:exact_message] || '')
    result << " - #{response[:bank_message]}" if response[:bank_message].present?
    result
  end
end
money_from_authorization(auth) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 427
def money_from_authorization(auth)
  _, _, amount = auth.split(/;/, 3)
  amount.to_i
end
name_node(root, node) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 479
def name_node(root, node)
  parent = root.name unless root.name == 'TransactionResult'
  "#{parent}#{node.name}".gsub(/EXact/, 'Exact').underscore.to_sym
end
parse(xml) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 457
def parse(xml)
  response = {}
  xml = REXML::Document.new(xml)

  if (root = REXML::XPath.first(xml, '//TransactionResult'))
    parse_elements(response, root)
  end

  SENSITIVE_FIELDS.each { |key| response.delete(key) }
  response
end
parse_elements(response, root) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 469
def parse_elements(response, root)
  root.elements.to_a.each do |node|
    if node.has_elements?
      parse_elements(response, node)
    else
      response[name_node(root, node)] = (node.text || '').strip
    end
  end
end
parse_error(error) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 444
def parse_error(error)
  {
    :transaction_approved => 'false',
    :error_number => error.code,
    :error_description => error.body,
    :ecommerce_error_code => error.body.gsub(/[^\d]/, '')
  }
end
response_authorization(action, response, credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 392
def response_authorization(action, response, credit_card)
  if action == :store
    store_authorization_from(response, credit_card)
  else
    authorization_from(response)
  end
end
standard_error_code(response) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 453
def standard_error_code(response)
  STANDARD_ERROR_CODE_MAPPING[response[:bank_resp_code] || response[:ecommerce_error_code]]
end
store_authorization_from(response, credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 412
def store_authorization_from(response, credit_card)
  if response[:transarmor_token].present?
    [
      response[:transarmor_token],
      credit_card.brand,
      credit_card.first_name,
      credit_card.last_name,
      credit_card.month,
      credit_card.year
    ].map { |value| value.to_s.tr(';', '') }.join(';')
  else
    raise StandardError, "TransArmor support is not enabled on your #{display_name} account"
  end
end
stored_credential_indicator(xml, card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 337
def stored_credential_indicator(xml, card, options)
  if card.brand == 'master' || options.dig(:stored_credential, :initial_transaction) == false
    'S'
  else
    '1'
  end
end
successful?(response) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 388
def successful?(response)
  response[:transaction_approved] == SUCCESS
end