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 300
def add_address(xml, options)
  if (address = options[:billing_address] || options[:address])
    address = strip_line_breaks(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 259
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)

    add_wallet_provider_id(xml, credit_card, options)
    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'
          # Payeezy requires an ECI of 5 for apple pay transactions
          # See: https://support.payeezy.com/hc/en-us/articles/203730589-Ecommerce-Flag-Values
          '05'
        else
          (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
        end

  xml.tag! 'Ecommerce_Flag', /^[0-9]+$/.match?(eci.to_s) ? 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 264
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)

  add_wallet_provider_id(xml, credit_card, options)
  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 234
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 294
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 322
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 332
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 247
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 336
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! 'Initiation', 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 327
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
add_wallet_provider_id(xml, credit_card, options) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 283
def add_wallet_provider_id(xml, credit_card, options)
  provider_id = if options[:wallet_provider_id]
                  options[:wallet_provider_id]
                elsif credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay
                  # See: https://support.payeezy.com/hc/en-us/articles/206601408-First-Data-Payeezy-Gateway-Web-Service-API-Reference-Guide#3.9
                  4
                end

  xml.tag! 'WalletProviderID', provider_id if provider_id
end
authorization_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 424
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 370
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 374
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 366
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 395
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 456
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 451
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 503
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 481
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 493
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 468
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 416
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 477
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 436
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 358
def stored_credential_indicator(xml, card, options)
  if card.brand == 'master' || options.dig(:stored_credential, :initial_transaction) == false
    'S'
  else
    '1'
  end
end
strip_line_breaks(address) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 316
def strip_line_breaks(address)
  return unless address.is_a?(Hash)

  address.map { |k, s| [k, s&.tr("\r\n", ' ')&.strip] }.to_h
end
successful?(response) click to toggle source
# File lib/active_merchant/billing/gateways/firstdata_e4_v27.rb, line 412
def successful?(response)
  response[:transaction_approved] == SUCCESS
end