require 'rexml/document' module ActiveMerchant #:nodoc: module Billing #:nodoc: class SecurePayAuGateway < Gateway API_VERSION = 'xml-4.2' PERIODIC_API_VERSION = 'spxml-3.0' class_attribute :test_periodic_url, :live_periodic_url self.test_url = 'https://test.api.securepay.com.au/xmlapi/payment' self.live_url = 'https://api.securepay.com.au/xmlapi/payment' self.test_periodic_url = 'https://test.securepay.com.au/xmlapi/periodic' self.live_periodic_url = 'https://api.securepay.com.au/xmlapi/periodic' self.supported_countries = ['AU'] self.supported_cardtypes = %i[visa master american_express diners_club jcb] # The homepage URL of the gateway self.homepage_url = 'http://securepay.com.au' # The name of the gateway self.display_name = 'SecurePay' class_attribute :request_timeout self.request_timeout = 60 self.money_format = :cents self.default_currency = 'AUD' # 0 Standard Payment # 4 Refund # 6 Client Reversal (Void) # 10 Preauthorise # 11 Preauth Complete (Advice) TRANSACTIONS = { purchase: 0, authorization: 10, capture: 11, void: 6, refund: 4 } PERIODIC_ACTIONS = { add_triggered: 'add', remove_triggered: 'delete', trigger: 'trigger' } PERIODIC_TYPES = { add_triggered: 4, remove_triggered: nil, trigger: nil } SUCCESS_CODES = %w[00 08 11 16 77] def initialize(options = {}) requires!(options, :login, :password) super end def purchase(money, credit_card_or_stored_id, options = {}) if credit_card_or_stored_id.respond_to?(:number) requires!(options, :order_id) commit :purchase, build_purchase_request(money, credit_card_or_stored_id, options) else options[:billing_id] = credit_card_or_stored_id.to_s commit_periodic(build_periodic_item(:trigger, money, nil, options)) end end def authorize(money, credit_card, options = {}) requires!(options, :order_id) commit :authorization, build_purchase_request(money, credit_card, options) end def capture(money, reference, options = {}) commit :capture, build_reference_request(money, reference) end def refund(money, reference, options = {}) commit :refund, build_reference_request(money, reference) end def credit(money, reference, options = {}) ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, reference) end def void(reference, options = {}) commit :void, build_reference_request(nil, reference) end def store(creditcard, options = {}) requires!(options, :billing_id, :amount) commit_periodic(build_periodic_item(:add_triggered, options[:amount], creditcard, options)) end def unstore(identification, options = {}) options[:billing_id] = identification commit_periodic(build_periodic_item(:remove_triggered, options[:amount], nil, options)) 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'). gsub(%r(().+()), '\1[FILTERED]\2') end private def build_purchase_request(money, credit_card, options) xml = Builder::XmlMarkup.new currency = options[:currency] || currency(money) xml.tag! 'amount', localized_amount(money, currency) xml.tag! 'currency', currency xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '') xml.tag! 'CreditCardInfo' do xml.tag! 'cardNumber', credit_card.number xml.tag! 'expiryDate', expdate(credit_card) xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? end xml.target! end def build_reference_request(money, reference) xml = Builder::XmlMarkup.new transaction_id, order_id, preauth_id, original_amount = reference.split('*') xml.tag! 'amount', (money ? amount(money) : original_amount) xml.tag! 'currency', options[:currency] || currency(money) xml.tag! 'txnID', transaction_id xml.tag! 'purchaseOrderNo', order_id xml.tag! 'preauthID', preauth_id xml.target! end def build_request(action, body) xml = Builder::XmlMarkup.new xml.instruct! xml.tag! 'SecurePayMessage' do xml.tag! 'MessageInfo' do xml.tag! 'messageID', SecureRandom.hex(15) xml.tag! 'messageTimestamp', generate_timestamp xml.tag! 'timeoutValue', request_timeout xml.tag! 'apiVersion', API_VERSION end xml.tag! 'MerchantInfo' do xml.tag! 'merchantID', @options[:login] xml.tag! 'password', @options[:password] end xml.tag! 'RequestType', 'Payment' xml.tag! 'Payment' do xml.tag! 'TxnList', 'count' => 1 do xml.tag! 'Txn', 'ID' => 1 do xml.tag! 'txnType', TRANSACTIONS[action] xml.tag! 'txnSource', 23 xml << body end end end end xml.target! end def commit(action, request) response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request))) Response.new( success?(response), message_from(response), response, test: test?, authorization: authorization_from(response) ) end def build_periodic_item(action, money, credit_card, options) xml = Builder::XmlMarkup.new xml.tag! 'actionType', PERIODIC_ACTIONS[action] xml.tag! 'clientID', options[:billing_id].to_s if credit_card xml.tag! 'CreditCardInfo' do xml.tag! 'cardNumber', credit_card.number xml.tag! 'expiryDate', expdate(credit_card) xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? end end xml.tag! 'amount', amount(money) xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action] xml.target! end def build_periodic_request(body) xml = Builder::XmlMarkup.new xml.instruct! xml.tag! 'SecurePayMessage' do xml.tag! 'MessageInfo' do xml.tag! 'messageID', SecureRandom.hex(15) xml.tag! 'messageTimestamp', generate_timestamp xml.tag! 'timeoutValue', request_timeout xml.tag! 'apiVersion', PERIODIC_API_VERSION end xml.tag! 'MerchantInfo' do xml.tag! 'merchantID', @options[:login] xml.tag! 'password', @options[:password] end xml.tag! 'RequestType', 'Periodic' xml.tag! 'Periodic' do xml.tag! 'PeriodicList', 'count' => 1 do xml.tag! 'PeriodicItem', 'ID' => 1 do xml << body end end end end xml.target! end def commit_periodic(request) my_request = build_periodic_request(request) response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, my_request)) Response.new( success?(response), message_from(response), response, test: test?, authorization: authorization_from(response) ) end def success?(response) SUCCESS_CODES.include?(response[:response_code]) end def authorization_from(response) [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*') end def message_from(response) response[:response_text] || response[:status_description] end def expdate(credit_card) "#{format(credit_card.month, :two_digits)}/#{format(credit_card.year, :two_digits)}" end def parse(body) xml = REXML::Document.new(body) response = {} xml.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 { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end end # YYYYDDMMHHNNSSKKK000sOOO def generate_timestamp time = Time.now.utc time.strftime("%Y%d%m%H%M%S#{time.usec}+000") end end end end