class ActiveMerchant::Billing::BlueSnapGateway

Constants

AVS_CODE_TRANSLATOR
BANK_ACCOUNT_TYPE_MAPPING
CVC_CODE_TRANSLATOR
SHOPPER_INITIATOR
STATE_CODE_COUNTRIES
TRANSACTIONS

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 75
def initialize(options = {})
  requires!(options, :api_username, :api_password)
  super
end

Public Instance Methods

authorize(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 92
def authorize(money, payment_method, options = {})
  commit(:authorize, options) do |doc|
    add_auth_purchase(doc, money, payment_method, options)
  end
end
capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 98
def capture(money, authorization, options = {})
  commit(:capture, options, :put) do |doc|
    add_authorization(doc, authorization)
    add_order(doc, options)
    add_amount(doc, money, options) if options[:include_capture_amount] == true
  end
end
purchase(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 80
def purchase(money, payment_method, options = {})
  payment_method_details = PaymentMethodDetails.new(payment_method)

  commit(:purchase, options, :post, payment_method_details) do |doc|
    if payment_method_details.alt_transaction?
      add_alt_transaction_purchase(doc, money, payment_method_details, options)
    else
      add_auth_purchase(doc, money, payment_method, options)
    end
  end
end
refund(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 106
def refund(money, authorization, options = {})
  options[:endpoint] = options[:merchant_transaction_id] ? "/refund/merchant/#{options[:merchant_transaction_id]}" : "/refund/#{authorization}"
  commit(:refund, options, :post) do |doc|
    add_amount(doc, money, options) if money
    %i[reason cancel_subscription tax_amount].each { |field| send_when_present(doc, field, options) }
    add_metadata(doc, options)
  end
end
scrub(transcript) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 167
def scrub(transcript)
  transcript.
    gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
    gsub(%r((<card-number>).+(</card-number>)), '\1[FILTERED]\2').
    gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2').
    gsub(%r((<(?:public-)?account-number>).+(</(?:public-)?account-number>)), '\1[FILTERED]\2').
    gsub(%r((<(?:public-)?routing-number>).+(</(?:public-)?routing-number>)), '\1[FILTERED]\2')
end
store(payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 126
def store(payment_method, options = {})
  payment_method_details = PaymentMethodDetails.new(payment_method)

  commit(:store, options, :post, payment_method_details) do |doc|
    add_personal_info(doc, payment_method, options)
    add_echeck_company(doc, payment_method) if payment_method_details.check?
    doc.send('payment-sources') do
      payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method)
    end
    add_order(doc, options)
  end
end
store_credit_card(doc, payment_method) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 139
def store_credit_card(doc, payment_method)
  doc.send('credit-card-info') do
    add_credit_card(doc, payment_method)
  end
end
store_echeck(doc, payment_method) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 145
def store_echeck(doc, payment_method)
  doc.send('ecp-info') do
    doc.send('ecp') do
      add_echeck(doc, payment_method)
    end
  end
end
supports_scrubbing?() click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 163
def supports_scrubbing?
  true
end
verify(payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 122
def verify(payment_method, options = {})
  authorize(0, payment_method, options)
end
verify_credentials() click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 153
def verify_credentials
  begin
    ssl_get(url.to_s, headers(options))
  rescue ResponseError => e
    return false if e.response.code.to_i == 401
  end

  true
end
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 115
def void(authorization, options = {})
  commit(:void, options, :put) do |doc|
    add_authorization(doc, authorization)
    add_order(doc, options)
  end
end

Private Instance Methods

add_3ds(doc, three_d_secure_options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 281
def add_3ds(doc, three_d_secure_options)
  eci = three_d_secure_options[:eci]
  cavv = three_d_secure_options[:cavv]
  xid = three_d_secure_options[:xid]
  ds_transaction_id = three_d_secure_options[:ds_transaction_id]
  version = three_d_secure_options[:version]

  doc.send('three-d-secure') do
    doc.eci(eci) if eci
    doc.cavv(cavv) if cavv
    doc.xid(xid) if xid
    doc.send('three-d-secure-version', version) if version
    doc.send('ds-transaction-id', ds_transaction_id) if ds_transaction_id
  end
end
add_address(doc, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 269
def add_address(doc, options)
  address = options[:billing_address]
  return unless address

  doc.country(address[:country]) if address[:country]
  doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country])
  doc.address(address[:address1]) if address[:address1]
  doc.address2(address[:address2]) if address[:address2]
  doc.city(address[:city]) if address[:city]
  doc.zip(address[:zip]) if address[:zip]
end
add_alt_transaction_purchase(doc, money, payment_method_details, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 367
def add_alt_transaction_purchase(doc, money, payment_method_details, options)
  doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
  doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
  doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
  add_amount(doc, money, options)

  vaulted_shopper_id = payment_method_details.vaulted_shopper_id
  doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id

  add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?) if payment_method_details.check?

  add_fraud_info(doc, payment_method_details.payment_method, options)
  add_stored_credentials(doc, options)
  add_metadata(doc, options)
end
add_amount(doc, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 209
def add_amount(doc, money, options)
  currency = options[:currency] || currency(money)
  doc.amount(localized_amount(money, currency))
  doc.currency(currency)
end
add_auth_purchase(doc, money, payment_method, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 178
def add_auth_purchase(doc, money, payment_method, options)
  doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE')
  add_order(doc, options)
  doc.send('store-card', options[:store_card] || false)
  add_amount(doc, money, options)
  add_fraud_info(doc, payment_method, options)
  add_stored_credentials(doc, options)

  if payment_method.is_a?(String)
    doc.send('vaulted-shopper-id', payment_method)
  else
    doc.send('card-holder-info') do
      add_personal_info(doc, payment_method, options)
    end
    add_credit_card(doc, payment_method)
  end
end
add_authorization(doc, authorization) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 334
def add_authorization(doc, authorization)
  doc.send('transaction-id', authorization)
end
add_credit_card(doc, card) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 224
def add_credit_card(doc, card)
  doc.send('credit-card') do
    doc.send('card-number', card.number)
    doc.send('security-code', card.verification_value)
    doc.send('expiration-month', card.month)
    doc.send('expiration-year', card.year)
  end
end
add_echeck(doc, check) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 402
def add_echeck(doc, check)
  doc.send('account-number', check.account_number)
  doc.send('routing-number', check.routing_number)
  doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"])
end
add_echeck_company(doc, check) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 398
def add_echeck_company(doc, check)
  doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business'
end
add_echeck_transaction(doc, check, options, vaulted_shopper) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 383
def add_echeck_transaction(doc, check, options, vaulted_shopper)
  unless vaulted_shopper
    doc.send('payer-info') do
      add_personal_info(doc, check, options)
      add_echeck_company(doc, check)
    end
  end

  doc.send('ecp-transaction') do
    add_echeck(doc, check) unless vaulted_shopper
  end

  doc.send('authorized-by-shopper', options[:authorized_by_shopper])
end
add_fraud_info(doc, payment_method, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 338
def add_fraud_info(doc, payment_method, options)
  doc.send('transaction-fraud-info') do
    doc.send('shopper-ip-address', options[:ip]) if options[:ip]
    if fraud_info = options[:transaction_fraud_info]
      doc.send('fraud-session-id', fraud_info[:fraud_session_id]) if fraud_info[:fraud_session_id]
    end
    unless payment_method.is_a? String
      doc.send('shipping-contact-info') do
        add_shipping_contact_info(doc, payment_method, options)
      end
    end
  end
end
add_level_3_data(doc, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 297
def add_level_3_data(doc, options)
  return unless options[:customer_reference_number]

  doc.send('level-3-data') do
    send_when_present(doc, :customer_reference_number, options)
    send_when_present(doc, :sales_tax_amount, options)
    send_when_present(doc, :freight_amount, options)
    send_when_present(doc, :duty_amount, options)
    send_when_present(doc, :destination_zip_code, options)
    send_when_present(doc, :destination_country_code, options)
    send_when_present(doc, :ship_from_zip_code, options)
    send_when_present(doc, :discount_amount, options)
    send_when_present(doc, :tax_amount, options)
    send_when_present(doc, :tax_rate, options)
    add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items]
  end
end
add_level_3_data_items(doc, items) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 323
def add_level_3_data_items(doc, items)
  items.each do |item|
    doc.send('level-3-data-item') do
      item.each do |key, value|
        key = key.to_s.dasherize
        doc.send(key, value)
      end
    end
  end
end
add_metadata(doc, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 233
def add_metadata(doc, options)
  transaction_meta_data = options[:transaction_meta_data] || []
  return if transaction_meta_data.empty? && !options[:description]

  doc.send('transaction-meta-data') do
    # ensure backwards compatibility for calls expecting :description
    # to become meta-data fields.
    if options[:description]
      doc.send('meta-data') do
        doc.send('meta-key', 'description')
        doc.send('meta-value', truncate(options[:description], 500))
        doc.send('meta-description', 'Description')
      end
    end

    # https://developers.bluesnap.com/v8976-XML/docs/meta-data
    transaction_meta_data.each do |entry|
      doc.send('meta-data') do
        doc.send('meta-key', truncate(entry[:meta_key], 40))
        doc.send('meta-value', truncate(entry[:meta_value], 500))
        doc.send('meta-description', truncate(entry[:meta_description], 40))
        doc.send('is-visible', truncate(entry[:meta_is_visible], 5))
      end
    end
  end
end
add_order(doc, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 260
def add_order(doc, options)
  doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
  doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
  doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
  add_metadata(doc, options)
  add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
  add_level_3_data(doc, options)
end
add_personal_info(doc, payment_method, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 215
def add_personal_info(doc, payment_method, options)
  doc.send('first-name', payment_method.first_name)
  doc.send('last-name', payment_method.last_name)
  doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number]
  doc.email(options[:email]) if options[:email]
  doc.phone(options[:phone_number]) if options[:phone_number]
  add_address(doc, options)
end
add_shipping_contact_info(doc, payment_method, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 352
def add_shipping_contact_info(doc, payment_method, options)
  if address = options[:shipping_address]
    # https://developers.bluesnap.com/v8976-XML/docs/shipping-contact-info
    doc.send('first-name', payment_method.first_name)
    doc.send('last-name', payment_method.last_name)

    doc.country(address[:country]) if address[:country]
    doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country])
    doc.address1(address[:address1]) if address[:address1]
    doc.address2(address[:address2]) if address[:address2]
    doc.city(address[:city]) if address[:city]
    doc.zip(address[:zip]) if address[:zip]
  end
end
add_stored_credentials(doc, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 196
def add_stored_credentials(doc, options)
  return unless stored_credential = options[:stored_credential]

  initiator = stored_credential[:initiator]&.upcase
  initiator = 'SHOPPER' if SHOPPER_INITIATOR.include?(initiator)
  doc.send('transaction-initiator', initiator) if stored_credential[:initiator]
  if stored_credential[:network_transaction_id]
    doc.send('network-transaction-info') do
      doc.send('original-network-transaction-id', stored_credential[:network_transaction_id])
    end
  end
end
api_request(action, request, verb, payment_method_details, options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 456
def api_request(action, request, verb, payment_method_details, options)
  ssl_request(verb, url(action, options, payment_method_details), request, headers(options))
rescue ResponseError => e
  e.response
end
authorization_from(action, parsed_response, payment_method_details) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 533
def authorization_from(action, parsed_response, payment_method_details)
  return vaulted_shopper_id(parsed_response, payment_method_details) if action == :store

  parsed_response['refund-transaction-id'] || parsed_response['transaction-id']
end
avs_lookup_key(p) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 495
def avs_lookup_key(p)
  "line1: #{p['avs-response-code-address']}, zip: #{p['avs-response-code-zip']}, name: #{p['avs-response-code-name']}"
end
avs_result(parsed) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 491
def avs_result(parsed)
  AVSResult.new(code: AVS_CODE_TRANSLATOR[avs_lookup_key(parsed)])
end
bad_authentication_response() click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 594
def bad_authentication_response
  { 'description' => 'Unable to authenticate.  Please check your credentials.' }
end
build_xml_request(action, payment_method_details) { |doc| ... } click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 576
def build_xml_request(action, payment_method_details)
  builder = Nokogiri::XML::Builder.new
  builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
    doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction? && action != :refund
    yield(doc)
  end
  builder.doc.root.to_xml
end
commit(action, options, verb = :post, payment_method_details = PaymentMethodDetails.new(), &block) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 462
def commit(action, options, verb = :post, payment_method_details = PaymentMethodDetails.new(), &block)
  request = build_xml_request(action, payment_method_details, &block)
  response = api_request(action, request, verb, payment_method_details, options)
  parsed = parse(response)

  succeeded = success_from(action, response)
  Response.new(
    succeeded,
    message_from(succeeded, response),
    parsed,
    authorization: authorization_from(action, parsed, payment_method_details),
    avs_result: avs_result(parsed),
    cvv_result: cvv_result(parsed),
    error_code: error_code_from(parsed),
    test: test?
  )
end
cvv_result(parsed) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 487
def cvv_result(parsed)
  CVVResult.new(CVC_CODE_TRANSLATOR[parsed['cvv-response-code']])
end
error_code_from(parsed_response) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 547
def error_code_from(parsed_response)
  parsed_response['code']
end
fraud_codes_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 514
def fraud_codes_from(response)
  event_summary = {}
  doc = Nokogiri::XML(response.body)
  fraud_events = doc.xpath('//xmlns:fraud-events', 'xmlns' => 'http://ws.plimus.com')
  fraud_events.children.each do |child|
    if child.children.children.any?
      event_summary[child.name] = event_summary[child.name] || []
      event = {}
      child.children.each do |chi|
        event[chi.name] = chi.text
      end
      event_summary[child.name] << event
    else
      event_summary[child.name] = child.text
    end
  end
  event_summary.to_json
end
generic_error_response(body) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 598
def generic_error_response(body)
  { 'description' => body }
end
handle_response(response) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 585
def handle_response(response)
  case response.code.to_i
  when 200...300
    response
  else
    raise ResponseError.new(response)
  end
end
headers(options) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 564
def headers(options)
  idempotency_key = options[:idempotency_key] if options[:idempotency_key]

  headers = {
    'Content-Type' => 'application/xml',
    'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip)
  }

  headers['Idempotency-Key'] = idempotency_key if idempotency_key
  headers
end
message_from(succeeded, response) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 503
def message_from(succeeded, response)
  return 'Success' if succeeded

  parsed = parse(response)
  if parsed.dig('error-name') == 'FRAUD_DETECTED'
    fraud_codes_from(response)
  else
    parsed['description']
  end
end
parse(response) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 408
def parse(response)
  return bad_authentication_response if response.code.to_i == 401
  return generic_error_response(response.body) if [403, 405, 429].include?(response.code.to_i)

  parsed = {}
  doc = Nokogiri::XML(response.body)
  doc.root.xpath('*').each do |node|
    name = node.name.downcase
    if node.elements.empty?
      parsed[name] = node.text
    elsif name == 'transaction-meta-data'
      metadata = []
      node.elements.each { |m|
        metadata.push parse_metadata_entry(m)
      }

      parsed['transaction-meta-data'] = metadata
    else
      node.elements.each { |childnode|
        parse_element(parsed, childnode)
      }
    end
  end

  parsed['content-location-header'] = response['content-location']
  parsed
end
parse_element(parsed, node) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 448
def parse_element(parsed, node)
  if node.elements.empty?
    parsed[node.name.downcase] = node.text
  else
    node.elements.each { |e| parse_element(parsed, e) }
  end
end
parse_metadata_entry(node) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 436
def parse_metadata_entry(node)
  entry = {}

  node.elements.each { |e|
    entry = entry.merge({
      e.name => e.text
    })
  }

  entry
end
root_attributes() click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 551
def root_attributes
  {
    xmlns: 'http://ws.plimus.com'
  }
end
root_element(action, payment_method_details) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 557
def root_element(action, payment_method_details)
  return 'refund' if action == :refund
  return 'vaulted-shopper' if action == :store

  payment_method_details.root_element
end
send_when_present(doc, options_key, options, xml_element_name = nil) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 315
def send_when_present(doc, options_key, options, xml_element_name = nil)
  return unless options[options_key]

  xml_element_name ||= options_key.to_s

  doc.send(xml_element_name.dasherize, options[options_key])
end
success_from(action, response) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 499
def success_from(action, response)
  (200...300).cover?(response.code.to_i)
end
url(action = nil, options = {}, payment_method_details = PaymentMethodDetails.new()) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 480
def url(action = nil, options = {}, payment_method_details = PaymentMethodDetails.new())
  base = test? ? test_url : live_url
  resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
  resource += options[:endpoint] if action == :refund
  "#{base}/#{resource}"
end
vaulted_shopper_id(parsed_response, payment_method_details) click to toggle source
# File lib/active_merchant/billing/gateways/blue_snap.rb, line 539
def vaulted_shopper_id(parsed_response, payment_method_details)
  return nil unless parsed_response['content-location-header']

  vaulted_shopper_id = parsed_response['content-location-header'].split('/').last
  vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction?
  vaulted_shopper_id
end