module ActiveMerchant #:nodoc: module Billing #:nodoc: module PayflowCommonAPI def self.included(base) base.default_currency = 'USD' # The certification_id is required by PayPal to make direct HTTPS posts to their servers. # The certification_id has been deprecated by PayPal. It will soon be removed and you can simply # use the certification_id that has been configured here, or generate your own base.class_inheritable_accessor :certification_id base.certification_id = '55d64dfec398cbbe66c1bf843cbad9' base.class_inheritable_accessor :partner # Set the default partner to PayPal base.partner = 'PayPal' base.supported_countries = ['US', 'CA', 'SG', 'AU'] end XMLNS = 'http://www.paypal.com/XMLPay' TEST_URL = 'https://pilot-payflowpro.verisign.com/transaction' LIVE_URL = 'https://payflowpro.verisign.com/transaction' CARD_MAPPING = { :visa => 'Visa', :master => 'MasterCard', :discover => 'Discover', :american_express => 'Amex', :jcb => 'JCB', :diners_club => 'DinersClub', :switch => 'Switch', :solo => 'Solo' } TRANSACTIONS = { :purchase => "Sale", :authorization => "Authorization", :capture => "Capture", :void => "Void", :credit => "Credit" } def initialize(options = {}) requires!(options, :login, :password) @options = { :certification_id => self.class.certification_id, :partner => self.class.partner }.update(options) super end def test? @options[:test] || Base.gateway_mode == :test end def capture(money, authorization, options = {}) request = build_reference_request(:capture, money, authorization, options) commit(request) end def void(authorization, options = {}) request = build_reference_request(:void, nil, authorization, options) commit(request) end private def build_request(body, request_type = nil) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! xml.tag! 'XMLPayRequest', 'Timeout' => 30, 'version' => "2.1", "xmlns" => XMLNS do xml.tag! 'RequestData' do xml.tag! 'Vendor', @options[:login] xml.tag! 'Partner', @options[:partner] if request_type == :recurring xml << body else xml.tag! 'Transactions' do xml.tag! 'Transaction' do xml.tag! '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 def build_reference_request(action, money, authorization, options) xml = Builder::XmlMarkup.new :indent => 2 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) end end end xml.target! end def add_address(xml, tag, address, options) return if address.nil? xml.tag! tag do xml.tag! 'Name', address[:name] unless options[:name].blank? xml.tag! 'Email', options[:email] unless options[:email].blank? xml.tag! 'Phone', address[:phone] unless address[:phone].blank? 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 def parse(data) response = {} xml = REXML::Document.new(data) root = REXML::XPath.first(xml, "//ResponseData") if REXML::XPath.first(root, "//TransactionResult/attribute::Duplicate") response[:duplicate] = true end root.elements.to_a.each do |node| parse_element(response, node) end response end def parse_element(response, node) if node.has_elements? node.elements.each{|e| parse_element(response, e) } elsif node.name == 'ExtData' response[node.attributes['Name'].underscore.to_sym] = node.attributes['Value'] else response[node.name.underscore.to_sym] = node.text end end def build_headers(content_length) { "Content-Type" => "text/xml", "Content-Length" => content_length.to_s, "X-VPS-Timeout" => "30", "X-VPS-VIT-Client-Certification-Id" => @options[:certification_id].to_s, "X-VPS-VIT-Integration-Product" => "ActiveMerchant", "X-VPS-VIT-Runtime-Version" => RUBY_VERSION, "X-VPS-Request-ID" => generate_unique_id } end def commit(request_body, request_type = nil) request = build_request(request_body, request_type) headers = build_headers(request.size) if result = test_result_from_cc_number(parse_credit_card_number(request)) return result end url = test? ? TEST_URL : LIVE_URL data = ssl_post(url, request, headers) @response = parse(data) success = @response[:result] == "0" message = @response[:message] build_response(success, message, @response, :test => test?, :authorization => @response[:pn_ref] || @response[:rp_ref] ) end def parse_credit_card_number(request) xml = REXML::Document.new(request) card_number = REXML::XPath.first(xml, '//Tender/Card/CardNum') card_number && card_number.text end end end end