module ActiveMerchant #:nodoc: module Billing #:nodoc: # See the remote test file for example usage. # # * All transactions use dollar values. class ConveniencePayGateway < Gateway TEST_URL = 'https://mo.paybill.com/WebServices/v3.6/PaybillService36.asmx' LIVE_URL = 'https://www.paybill.com/WebServices/v3.6/PaybillService36.asmx' # visa, master, american_express, discover self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.supported_countries = ['US'] self.default_currency = 'USD' self.homepage_url = 'http://www.paybill.com' self.display_name = 'HP Convenience Pay Services' # These are the options that can be used when creating a new Convenience Pay Gateway object. # # :login => your username # # :password => your password # # :test => true sets the gateway to test mode def initialize(options = {}) requires!(options, :login, :password, :client_id, :transaction_type) @options = options super end # Should run against the test servers or not? def test? @options[:test] || Base.gateway_mode == :test end def purchase(money, creditcard, options = {}) requires!(options, :order_id) setup_address_hash(options) commit('AuthorizeCreditCard', build_purchase_request(money, creditcard, options), options) end def void(identification, options = {}) commit('VoidPayment', build_void_request(identification, options), options) end private # Create all address hash key value pairs so that we still function if we were only provided with one or two of them def setup_address_hash(options) options[:billing_address] = options[:billing_address] || options[:address] || {} options[:shipping_address] = options[:shipping_address] || {} end def build_purchase_request(money, creditcard, options) xml = Builder::XmlMarkup.new :indent => 2 add_address(xml, creditcard, options[:billing_address], options) add_purchase_data(xml, money, options) add_creditcard(xml, creditcard) xml.target! end def build_void_request(transaction_id, options) xml = Builder::XmlMarkup.new :indent => 2 add_void_data(xml, transaction_id, options) xml.target! end def add_merchant_data(xml, options) xml.tag! 'ClientId', @options[:client_id] xml.tag! 'TransactionType', @options[:transaction_type] end def add_void_data(xml, transaction_id, options) xml.tag! 'ClientAccountNumber', options[:order_id] xml.tag! 'TransactionId', transaction_id end def add_purchase_data(xml, money = 0, options={}) xml.tag! 'PaymentAmount', amount(money) xml.tag! 'ClientAccountNumber', options[:order_id] xml.tag! 'ClientUserId', nil xml.tag! 'AdditionalData', nil end def add_address(xml, creditcard, address, options) xml.tag! 'CustomerFirstName', creditcard.first_name xml.tag! 'CustomerLastName', creditcard.last_name xml.tag! 'NotifyCustomerEmail', nil xml.tag! 'ZipCode', address[:zip] end def add_creditcard(xml, creditcard) xml.tag! 'PaymentAccountNumber', creditcard.number xml.tag! 'ExpirationDate', "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}" xml.tag! 'VerificationCode', creditcard.verification_value end # Where we actually build the full SOAP request using builder def build_request(transaction_type, body, options) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! xml.tag! 'soap:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'} do xml.tag! 'soap:Header' do xml.tag! 'PaybillCredentials', {'xmlns' => 'http://www.paybill.com/'} do xml.tag! 'Username', @options[:login] xml.tag! 'Password', @options[:password] end end xml.tag! 'soap:Body' do xml.tag! transaction_type, {'xmlns' => 'http://www.paybill.com/'} do xml.tag! 'request' do add_merchant_data(xml, options) xml << body end end end end xml.target! end # Contact HP Convenience Pay Services, make the SOAP request, and parse the reply into a Response object def commit(transaction_type, request, options) response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, build_request(transaction_type, request, options), { "Content-Type" => "text/xml" })) success = response[:decision] == "true" message = response[:message] authorization = response[:transaction_id] Response.new(success, message, response, :test => test?, :authorization => authorization ) end # Parse the SOAP response # Technique inspired by the Paypal Gateway def parse(xml) reply = {} xml = REXML::Document.new(xml) if root = REXML::XPath.first(xml, "//AuthorizeCreditCardResult") root.elements.to_a.each do |node| case node.name when 'ResponseText' reply[:message] = node.text when 'Authorized' reply[:decision] = node.text when 'ConfirmationNumber' reply[:confirmation_number] = node.text when 'TransactionId' reply[:transaction_id] = node.text else parse_element(reply, node) end end elsif root = REXML::XPath.first(xml, "//VoidPaymentResult") root.elements.to_a.each do |node| case node.name when 'VoidResponseText' reply[:message] = node.text when 'IsVoided' reply[:decision] = node.text when 'VoidConfirmationNumber' reply[:confirmation_number] = node.text when 'VoidDateTime' reply[:void_date_time] = node.text else parse_element(reply, node) end end elsif root = REXML::XPath.first(xml, "//soap:Fault") parse_element(reply, root) reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}" end return reply end def parse_element(reply, node) if node.has_elements? node.elements.each{|e| parse_element(reply, e) } else if node.parent.name =~ /item/ parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '') reply[(parent + '_' + node.name).to_sym] = node.text else reply[node.name.to_sym] = node.text end end return reply end end end end