module ActiveMerchant::Billing::PayflowCommonAPI

Constants

CARD_MAPPING
CVV_CODE
TRANSACTIONS
XMLNS

Public Class Methods

included(base) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 5
def self.included(base)
  base.default_currency = 'USD'

  base.class_attribute :partner

  # Set the default partner to PayPal
  base.partner = 'PayPal'

  base.supported_countries = ['US', 'CA', 'SG', 'AU']

  base.class_attribute :timeout
  base.timeout = 60

  base.test_url = 'https://pilot-payflowpro.paypal.com'
  base.live_url = 'https://payflowpro.paypal.com'

  # Enable safe retry of failed connections
  # Payflow is safe to retry because retried transactions use the same
  # X-VPS-Request-ID header. If a transaction is detected as a duplicate
  # only the original transaction data will be used by Payflow, and the
  # subsequent Responses will have a :duplicate parameter set in the params
  # hash.
  base.retry_safe = true
end
new(options = {}) click to toggle source
Calls superclass method
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 58
def initialize(options = {})
  requires!(options, :login, :password)

  options[:partner] = partner if options[:partner].blank?
  super
end

Public Instance Methods

capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 65
def capture(money, authorization, options = {})
  request = build_reference_request(:capture, money, authorization, options)
  commit(request, options)
end
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 70
def void(authorization, options = {})
  request = build_reference_request(:void, nil, authorization, options)
  commit(request, options)
end

Private Instance Methods

add_address(xml, tag, address, options) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 122
def add_address(xml, tag, address, options)
  return if address.nil?
  xml.tag! tag do
    xml.tag! 'Name', address[:name] unless address[:name].blank?
    xml.tag! 'EMail', options[:email] unless options[:email].blank?
    xml.tag! 'Phone', address[:phone] unless address[:phone].blank?
    xml.tag! 'CustCode', options[:customer] if !options[:customer].blank? && tag == 'BillTo'
    xml.tag! 'PONum', options[:po_number] if !options[:po_number].blank? && tag == 'BillTo'

    xml.tag! 'Address' do
      xml.tag! 'Street', address[:address1] unless address[:address1].blank?
      xml.tag! 'City', address[:city] unless address[:city].blank?
      xml.tag! 'State', address[:state].blank? ? "N/A" : address[:state]
      xml.tag! 'Country', address[:country] unless address[:country].blank?
      xml.tag! 'Zip', address[:zip] unless address[:zip].blank?
    end
  end
end
build_headers(content_length) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 183
def build_headers(content_length)
  {
    "Content-Type" => "text/xml",
    "Content-Length" => content_length.to_s,
    "X-VPS-Client-Timeout" => timeout.to_s,
    "X-VPS-VIT-Integration-Product" => "ActiveMerchant",
    "X-VPS-VIT-Runtime-Version" => RUBY_VERSION,
    "X-VPS-Request-ID" => SecureRandom.hex(16)
  }
end
build_reference_request(action, money, authorization, options) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 104
def build_reference_request(action, money, authorization, options)
  xml = Builder::XmlMarkup.new
  xml.tag! TRANSACTIONS[action] do
    xml.tag! 'PNRef', authorization

    unless money.nil?
      xml.tag! 'Invoice' do
        xml.tag!('TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money))
        xml.tag!('Description', options[:description]) unless options[:description].blank?
        xml.tag!('Comment', options[:comment]) unless options[:comment].blank?
        xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank?
      end
    end
  end

  xml.target!
end
build_request(body, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 76
def build_request(body, options = {})
  xml = Builder::XmlMarkup.new
  xml.instruct!
  xml.tag! 'XMLPayRequest', 'Timeout' => timeout.to_s, 'version' => "2.1", "xmlns" => XMLNS do
    xml.tag! 'RequestData' do
      xml.tag! 'Vendor', @options[:login]
      xml.tag! 'Partner', @options[:partner]
      if options[:request_type] == :recurring
        xml << body
      else
        xml.tag! 'Transactions' do
          xml.tag! 'Transaction', 'CustRef' => options[:customer] do
            xml.tag! 'Verbosity', @options[:verbosity] || 'MEDIUM'
            xml << body
          end
        end
      end
    end
    xml.tag! 'RequestAuth' do
      xml.tag! 'UserPass' do
        xml.tag! 'User', !@options[:user].blank? ? @options[:user] : @options[:login]
        xml.tag! 'Password', @options[:password]
      end
    end
  end
  xml.target!
end
commit(request_body, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 194
def commit(request_body, options  = {})
  request = build_request(request_body, options)
  headers = build_headers(request.size)

  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))

  build_response(response[:result] == "0", response[:message], response,
    :test => test?,
    :authorization => response[:pn_ref] || response[:rp_ref],
    :cvv_result => CVV_CODE[response[:cv_result]],
    :avs_result => { :code => response[:avs_result] }
  )
end
parse(data) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 141
def parse(data)
  response = {}
  xml = Nokogiri::XML(data)
  xml.remove_namespaces!
  root = xml.xpath("//ResponseData")

  # REXML::XPath in Ruby 1.8.6 is now unable to match nodes based on their attributes
  tx_result = root.xpath(".//TransactionResult").first

  if tx_result && tx_result.attributes['Duplicate'].to_s == "true"
    response[:duplicate] = true
  end

  root.xpath(".//*").each do |node|
    parse_element(response, node)
  end

  response
end
parse_element(response, node) click to toggle source
# File lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb, line 161
def parse_element(response, node)
  node_name = node.name.underscore.to_sym
  case
  when node_name == :rp_payment_result
    # Since we'll have multiple history items, we can't just flatten everything
    # down as we do everywhere else. RPPaymentResult elements are not contained
    # in an RPPaymentResults element so we'll come here multiple times
    response[node_name] ||= []
    response[node_name] << ( payment_result_response = {} )
    node.xpath(".//*").each{ |e| parse_element(payment_result_response, e) }
  when node.xpath(".//*").to_a.any?
    node.xpath(".//*").each{|e| parse_element(response, e) }
  when node_name.to_s =~ /amt$/
    # *Amt elements don't put the value in the #text - instead they use a Currency attribute
    response[node_name] = node.attributes['Currency'].to_s
  when node_name == :ext_data
    response[node.attributes['Name'].to_s.underscore.to_sym] = node.attributes['Value'].to_s
  else
    response[node_name] = node.text
  end
end