require 'rexml/document' module ActiveMerchant #:nodoc: module Billing #:nodoc: class MonerisGateway < Gateway attr_reader :url attr_reader :response attr_reader :options TEST_URL = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest' LIVE_URL = 'https://www3.moneris.com/gateway2/servlet/MpgRequest' # login is your Store ID # password is your API Token def initialize(options = {}) requires!(options, :login, :password) @options = { :strict_ssl => true, :crypt_type => 7 }.update(options) @url = test? ? TEST_URL : LIVE_URL super end def authorize(money, creditcard, options = {}) requires!(options, :order_id) parameters = { :order_id => options[:order_id], :cust_id => options[:customer], :amount => amount(money), :pan => creditcard.number, :expdate => expdate(creditcard), :crypt_type => options[:crypt_type] || @options[:crypt_type] } commit('preauth', parameters) end # Pass in <tt>order_id</tt> and optionally a <tt>customer</tt> parameter def purchase(money, creditcard, options = {}) requires!(options, :order_id) parameters = { :order_id => options[:order_id], :cust_id => options[:customer], :amount => amount(money), :pan => creditcard.number, :expdate => expdate(creditcard), :crypt_type => options[:crypt_type] || @options[:crypt_type] } commit('purchase', parameters) end # Moneris requires both the order_id and the transaction number of # the original authorization. To maintain the same interface as the other # gateways the two numbers are concatenated together with an _ separator as # the authorization number returned by authorization def capture(money, authorization, options = {}) txn_number, order_id = authorization.split(';') parameters = { :txn_number => txn_number, :order_id => order_id, :comp_amount => amount(money), :crypt_type => options[:crypt_type] || @options[:crypt_type] } commit('completion', parameters) end def void(authorization, options = {}) txn_number, order_id = authorization.split(';') parameters = { :txn_number => txn_number, :order_id => order_id, :crypt_type => options[:crypt_type] || @options[:crypt_type] } commit('purchasecorrection', parameters) end # We support visa and master card def self.supported_cardtypes [:visa, :master] end private def expdate(creditcard) year = sprintf("%.4i", creditcard.year) month = sprintf("%.2i", creditcard.month) "#{year[-2..-1]}#{month}" end def commit(action, parameters) if result = test_result_from_cc_number(parameters[:pan]) return result end data = ssl_post @url, post_data(action, parameters) @response = parse(data) success = (response[:response_code] and response[:complete] and (0..49).include?(response[:response_code].to_i) ) message = message_form(response[:message]) authorization = "#{response[:trans_id]};#{response[:receipt_id]}" if response[:trans_id] && response[:receipt_id] Response.new(success, message, @response, :test => test?, :authorization => authorization ) end # Parse moneris response xml into a convinient hash def parse(xml) # "<?xml version=\"1.0\"?><response><receipt>". # "<ReceiptId>Global Error Receipt</ReceiptId>". # "<ReferenceNum>null</ReferenceNum> # <ResponseCode>null</ResponseCode>". # "<ISO>null</ISO> # <AuthCode>null</AuthCode> # <TransTime>null</TransTime>". # "<TransDate>null</TransDate> # <TransType>null</TransType> # <Complete>false</Complete>". # "<Message>null</Message> # <TransAmount>null</TransAmount>". # "<CardType>null</CardType>". # "<TransID>null</TransID> # <TimedOut>null</TimedOut>". # "</receipt></response> response = {:message => "Global Error Receipt", :complete => false} xml = REXML::Document.new(xml) xml.elements.each('//receipt/*') do |node| response[node.name.underscore.to_sym] = normalize(node.text) end unless xml.root.nil? response end def post_data(action, parameters = {}) xml = REXML::Document.new root = xml.add_element("request") root.add_element("store_id").text = options[:login] root.add_element("api_token").text = options[:password] transaction = root.add_element(action) # Must add the elements in the correct order actions[action].each do |key| transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? end xml.to_s end def message_form(message) return 'Unspecified error' if message.blank? message.gsub(/[^\w]/, ' ').split.join(" ").capitalize end # Make a ruby type out of the response string def normalize(field) case field when "true" then true when "false" then false when "" then nil when "null" then nil else field end end def actions ACTIONS end ACTIONS = { "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], "command" => [:order_id], "refund" => [:order_id, :amount, :txn_number, :crypt_type], "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type], "purchasecorrection" => [:order_id, :txn_number, :crypt_type], "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav], "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], "Batchcloseall" => [], "opentotals" => [:ecr_number], "batchclose" => [:ecr_number], } end end end