require 'nokogiri' module ActiveMerchant #:nodoc: module Billing #:nodoc: class BpointGateway < Gateway self.live_url = 'https://www.bpoint.com.au/evolve/service_1_4_4.asmx' self.supported_countries = ['AU'] self.default_currency = 'AUD' self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] self.homepage_url = 'https://www.bpoint.com.au/bpoint' self.display_name = 'BPoint' def initialize(options={}) requires!(options, :username, :password, :merchant_number) super end def store(credit_card, options={}) options[:crn1] ||= 'DEFAULT' request_body = soap_request do |xml| add_token(xml, credit_card, options) end commit(request_body) end def purchase(amount, credit_card, options={}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_purchase(payment_xml, amount, credit_card, options) end end commit(request_body) end def authorize(amount, credit_card, options={}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_authorize(payment_xml, amount, credit_card, options) end end commit(request_body) end def capture(amount, authorization, options={}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_capture(payment_xml, amount, authorization, options) end end commit(request_body) end def refund(amount, authorization, options={}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_refund(payment_xml, amount, authorization, options) end end commit(request_body) end def void(amount, authorization, options={}) request_body = soap_request do |xml| process_payment(xml) do |payment_xml| add_void(payment_xml, amount, authorization, options) end end commit(request_body) end def verify(credit_card, options={}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(100, r.authorization, options) } end end def supports_scrubbing? true end def scrub(transcript) transcript. gsub(%r(().+()), '\1[FILTERED]\2'). gsub(%r(().+()), '\1[FILTERED]\2'). gsub(%r(().+()), '\1[FILTERED]\2') end private def soap_request Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| xml.send('soap12:Envelope', soap_envelope_attributes) { xml.send('soap12:Body') { yield(xml) if block_given? } } end.to_xml end def soap_envelope_attributes { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope' } end def process_payment(xml) xml.send('ProcessPayment', { 'xmlns' => 'urn:Eve_1_4_4' }) { credentials_xml(xml) xml.send('txnReq') { yield(xml) if block_given? } } end def add_token(xml, credit_card, options) xml.send('AddToken', { 'xmlns' => 'urn:Eve_1_4_4' }) { credentials_xml(xml) xml.send('tokenRequest') { xml.send('CRN1', options[:crn1]) xml.send('CRN2', '') xml.send('CRN3', '') xml.send('CardNumber', credit_card.number) xml.send('ExpiryDate', expdate(credit_card)) } } end def credentials_xml(xml) xml.send('username', @options[:username]) xml.send('password', @options[:password]) xml.send('merchantNumber', @options[:merchant_number]) end def add_purchase(xml, amount, credit_card, options) payment_xml(xml, 'PAYMENT', amount, options) credit_card_xml(xml, credit_card) end def add_authorize(xml, amount, credit_card, options) payment_xml(xml, 'PREAUTH', amount, options) credit_card_xml(xml, credit_card) end def add_capture(xml, amount, transaction_number, options) payment_xml(xml, 'CAPTURE', amount, options) transaction_number_xml(xml, transaction_number) end def add_refund(xml, amount, transaction_number, options) payment_xml(xml, 'REFUND', amount, options) transaction_number_xml(xml, transaction_number) end def add_void(xml, amount, transaction_number, options) payment_xml(xml, 'REVERSAL', amount, options) transaction_number_xml(xml, transaction_number) end def payment_xml(xml, payment_type, amount, options) xml.send('PaymentType', payment_type) xml.send('TxnType', 'WEB_SHOP') xml.send('BillerCode', options.fetch(:biller_code, '')) xml.send('MerchantReference', options[:order_id]) if options[:order_id] xml.send('CRN1', options[:crn1]) if options[:crn1] xml.send('CRN2', options[:crn2]) if options[:crn2] xml.send('CRN3', options[:crn3]) if options[:crn3] xml.send('Amount', amount) end def credit_card_xml(xml, credit_card) xml.send('CardNumber', credit_card.number) xml.send('ExpiryDate', expdate(credit_card)) xml.send('CVC', credit_card.verification_value) end def transaction_number_xml(xml, transaction_number) xml.send('OriginalTransactionNumber', transaction_number) end def commit(request_body) parse(ssl_post(live_url, request_body, request_headers)) end def request_headers { 'Content-Type' => 'application/soap+xml; charset=utf-8' } end def parse(body) response_for(Nokogiri::XML(body).remove_namespaces!) end def response_for(xml_doc) if xml_doc.xpath('//ProcessPaymentResponse').any? ProcessPaymentResponse.new(xml_doc, self).to_response elsif xml_doc.xpath('//AddTokenResponse').any? AddTokenResponse.new(xml_doc, self).to_response end end class BPointResponse attr_reader :xml_doc, :gateway, :params def initialize(xml_doc, gateway) @xml_doc = xml_doc @gateway = gateway @params = init_params end def to_response Response.new(success?, message, params, options) end private def init_params {}.tap do |h| xml_doc.xpath(response_node).each do |node| if node.elements.empty? h[node.name.to_sym] = node.text else node.elements.each do |childnode| name = "#{node.name}_#{childnode.name}" h[name.to_sym] = childnode.text end end end end end def response_node "//#{self.class.name.split('::').last}/*" end def options { authorization: params[authorization_key], test: gateway.test? } end end class ProcessPaymentResponse < BPointResponse private def authorization_key :ProcessPaymentResult_TransactionNumber end def success? params[:ProcessPaymentResult_ResponseCode] == '0' end def message params[:ProcessPaymentResult_AuthorisationResult] || params[:response_ResponseMessage] end end class AddTokenResponse < BPointResponse private def authorization_key :AddTokenResult_Token end def success? params[:response_ResponseCode] == 'SUCCESS' end def message params[:response_ResponseCode].capitalize end end end end end