require 'rexml/document' module ActiveMerchant #:nodoc: module Billing #:nodoc: # Public: For more information on the Eway Gateway please visit their # {Developers Area}[http://www.eway.com.au/developers/api/direct-payments] class EwayGateway < Gateway self.live_url = 'https://www.eway.com.au' self.money_format = :cents self.supported_countries = ['AU', 'NZ', 'GB'] self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] self.homepage_url = 'http://www.eway.com.au/' self.display_name = 'eWAY' # Public: Create a new Eway Gateway. # options - A hash of options: # :login - Your Customer ID. # :password - Your XML Refund Password that you # specified on the Eway site. (optional) def initialize(options = {}) requires!(options, :login) super end def purchase(money, creditcard, options = {}) requires_address!(options) post = {} add_creditcard(post, creditcard) add_address(post, options) add_customer_id(post) add_invoice_data(post, options) add_non_optional_data(post) add_amount(post, money) post[:CustomerEmail] = options[:email] commit(purchase_url(post[:CVN]), money, post) end def refund(money, authorization, options={}) post = {} add_customer_id(post) add_amount(post, money) add_non_optional_data(post) post[:OriginalTrxnNumber] = authorization post[:RefundPassword] = @options[:password] post[:CardExpiryMonth] = nil post[:CardExpiryYear] = nil commit(refund_url, money, post) end private def requires_address!(options) raise ArgumentError.new("Missing eWay required parameters: address or billing_address") unless (options.has_key?(:address) or options.has_key?(:billing_address)) end def add_creditcard(post, creditcard) post[:CardNumber] = creditcard.number post[:CardExpiryMonth] = sprintf("%.2i", creditcard.month) post[:CardExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1] post[:CustomerFirstName] = creditcard.first_name post[:CustomerLastName] = creditcard.last_name post[:CardHoldersName] = creditcard.name post[:CVN] = creditcard.verification_value if creditcard.verification_value? end def add_address(post, options) if address = options[:billing_address] || options[:address] post[:CustomerAddress] = [ address[:address1], address[:address2], address[:city], address[:state], address[:country] ].compact.join(', ') post[:CustomerPostcode] = address[:zip] end end def add_customer_id(post) post[:CustomerID] = @options[:login] end def add_invoice_data(post, options) post[:CustomerInvoiceRef] = options[:order_id] post[:CustomerInvoiceDescription] = options[:description] end def add_amount(post, money) post[:TotalAmount] = amount(money) end def add_non_optional_data(post) post[:Option1] = nil post[:Option2] = nil post[:Option3] = nil post[:TrxnNumber] = nil end def commit(url, money, parameters) raw_response = ssl_post(url, post_data(parameters)) response = parse(raw_response) Response.new(success?(response), message_from(response[:ewaytrxnerror]), response, :authorization => response[:ewaytrxnnumber], :test => test? ) end def success?(response) response[:ewaytrxnstatus] == "True" end def parse(xml) response = {} xml = REXML::Document.new(xml) xml.elements.each('//ewayResponse/*') do |node| response[node.name.downcase.to_sym] = normalize(node.text) end unless xml.root.nil? response end def post_data(parameters = {}) xml = REXML::Document.new root = xml.add_element("ewaygateway") parameters.each do |key, value| root.add_element("eway#{key}").text = value end xml.to_s end def message_from(message) return '' if message.blank? MESSAGES[message[0,2]] || message 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 purchase_url(cvn) suffix = test? ? 'xmltest/testpage.asp' : 'xmlpayment.asp' gateway_part = cvn ? 'gateway_cvn' : 'gateway' "#{live_url}/#{gateway_part}/#{suffix}" end def refund_url suffix = test? ? 'xmltest/refund_test.asp' : 'xmlpaymentrefund.asp' "#{live_url}/gateway/#{suffix}" end MESSAGES = { "00" => "Transaction Approved", "01" => "Refer to Issuer", "02" => "Refer to Issuer, special", "03" => "No Merchant", "04" => "Pick Up Card", "05" => "Do Not Honour", "06" => "Error", "07" => "Pick Up Card, Special", "08" => "Honour With Identification", "09" => "Request In Progress", "10" => "Approved For Partial Amount", "11" => "Approved, VIP", "12" => "Invalid Transaction", "13" => "Invalid Amount", "14" => "Invalid Card Number", "15" => "No Issuer", "16" => "Approved, Update Track 3", "19" => "Re-enter Last Transaction", "21" => "No Action Taken", "22" => "Suspected Malfunction", "23" => "Unacceptable Transaction Fee", "25" => "Unable to Locate Record On File", "30" => "Format Error", "31" => "Bank Not Supported By Switch", "33" => "Expired Card, Capture", "34" => "Suspected Fraud, Retain Card", "35" => "Card Acceptor, Contact Acquirer, Retain Card", "36" => "Restricted Card, Retain Card", "37" => "Contact Acquirer Security Department, Retain Card", "38" => "PIN Tries Exceeded, Capture", "39" => "No Credit Account", "40" => "Function Not Supported", "41" => "Lost Card", "42" => "No Universal Account", "43" => "Stolen Card", "44" => "No Investment Account", "51" => "Insufficient Funds", "52" => "No Cheque Account", "53" => "No Savings Account", "54" => "Expired Card", "55" => "Incorrect PIN", "56" => "No Card Record", "57" => "Function Not Permitted to Cardholder", "58" => "Function Not Permitted to Terminal", "59" => "Suspected Fraud", "60" => "Acceptor Contact Acquirer", "61" => "Exceeds Withdrawal Limit", "62" => "Restricted Card", "63" => "Security Violation", "64" => "Original Amount Incorrect", "66" => "Acceptor Contact Acquirer, Security", "67" => "Capture Card", "75" => "PIN Tries Exceeded", "82" => "CVV Validation Error", "90" => "Cutoff In Progress", "91" => "Card Issuer Unavailable", "92" => "Unable To Route Transaction", "93" => "Cannot Complete, Violation Of The Law", "94" => "Duplicate Transaction", "96" => "System Error" } end end end