lib/active_merchant/billing/gateways/realex.rb in activemerchant-1.13.0 vs lib/active_merchant/billing/gateways/realex.rb in activemerchant-1.14.0

- old
+ new

@@ -1,180 +1,302 @@ require 'rexml/document' require 'digest/sha1' module ActiveMerchant module Billing - # Realex us the leading CC gateway in Ireland + # Realex is the leading CC gateway in Ireland # see http://www.realexpayments.com # Contributed by John Ward (john@ward.name) # see http://thinedgeofthewedge.blogspot.com - # + # # Realex works using the following # login - The unique id of the merchant # password - The secret is used to digitally sign the request # account - This is an optional third part of the authentication process # and is used if the merchant wishes do distuinguish cc traffic from the different sources # by using a different account. This must be created in advance # - # the Realex team decided to make the orderid unique per request, - # so if validation fails you can not correct and resend using the + # the Realex team decided to make the orderid unique per request, + # so if validation fails you can not correct and resend using the # same order id class RealexGateway < Gateway URL = 'https://epage.payandshop.com/epage-remote.cgi' - + CARD_MAPPING = { 'master' => 'MC', 'visa' => 'VISA', 'american_express' => 'AMEX', 'diners_club' => 'DINERS', 'switch' => 'SWITCH', 'solo' => 'SWITCH', 'laser' => 'LASER' } - + self.money_format = :cents self.default_currency = 'EUR' self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :switch, :solo, :laser ] self.supported_countries = [ 'IE', 'GB' ] self.homepage_url = 'http://www.realexpayments.com/' self.display_name = 'Realex' - + SUCCESS, DECLINED = "Successful", "Declined" BANK_ERROR = REALEX_ERROR = "Gateway is in maintenance. Please try again later." ERROR = CLIENT_DEACTIVATED = "Gateway Error" - + def initialize(options = {}) requires!(options, :login, :password) + options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options.has_key?(:rebate_secret) @options = options super - end - + end + def purchase(money, credit_card, options = {}) requires!(options, :order_id) - - request = build_purchase_or_authorization_request(:purchase, money, credit_card, options) + + request = build_purchase_or_authorization_request(:purchase, money, credit_card, options) commit(request) - end - - private - def commit(request) + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + + request = build_purchase_or_authorization_request(:authorization, money, creditcard, options) + commit(request) + end + + def capture(money, authorization, options = {}) + request = build_capture_request(authorization, options) + commit(request) + end + + def refund(money, authorization, options = {}) + request = build_refund_request(money, authorization, options) + commit(request) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def void(authorization, options = {}) + request = build_void_request(authorization, options) + commit(request) + end + + private + def commit(request) response = parse(ssl_post(URL, request)) Response.new(response[:result] == "00", message_from(response), response, :test => response[:message] =~ /\[ test system \]/, - :authorization => response[:authcode], - :cvv_result => response[:cvnresult] - ) + :authorization => authorization_from(response), + :cvv_result => response[:cvnresult], + :avs_result => { + :street_match => response[:avspostcoderesponse], + :postal_match => response[:avspostcoderesponse] + } + ) end def parse(xml) response = {} - - xml = REXML::Document.new(xml) + + xml = REXML::Document.new(xml) xml.elements.each('//response/*') do |node| if (node.elements.size == 0) response[node.name.downcase.to_sym] = normalize(node.text) else node.elements.each do |childnode| name = "#{node.name.downcase}_#{childnode.name.downcase}" response[name.to_sym] = normalize(childnode.text) - end + end end end unless xml.root.nil? response end - - def parse_credit_card_number(request) - xml = REXML::Document.new(request) - card_number = REXML::XPath.first(xml, '/request/card/number') - card_number && card_number.text + + def authorization_from(parsed) + [parsed[:orderid], parsed[:pasref], parsed[:authcode]].join(';') end def build_purchase_or_authorization_request(action, money, credit_card, options) - timestamp = Time.now.strftime('%Y%m%d%H%M%S') - + timestamp = new_timestamp xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'auth' do - - xml.tag! 'merchantid', @options[:login] - xml.tag! 'account', @options[:account] - + add_merchant_details(xml, options) xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + add_amount(xml, money, options) + add_card(xml, credit_card) + xml.tag! 'autosettle', 'flag' => auto_settle_flag(action) + add_signed_digest(xml, timestamp, @options[:login], options[:order_id], amount(money), (options[:currency] || currency(money)), credit_card.number) + add_comments(xml, options) + add_address_and_customer_info(xml, options) + end + xml.target! + end + + def build_capture_request(authorization, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'settle' do + add_merchant_details(xml, options) + add_transaction_identifiers(xml, authorization, options) + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], options[:order_id]) + end + xml.target! + end + + def build_refund_request(money, authorization, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'rebate' do + add_merchant_details(xml, options) + add_transaction_identifiers(xml, authorization, options) xml.tag! 'amount', amount(money), 'currency' => options[:currency] || currency(money) + xml.tag! 'refundhash', @options[:refund_hash] if @options[:refund_hash] + xml.tag! 'autosettle', 'flag' => 1 + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], options[:order_id], amount(money), (options[:currency] || currency(money))) + end + xml.target! + end - xml.tag! 'card' do - xml.tag! 'number', credit_card.number - xml.tag! 'expdate', expiry_date(credit_card) - xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s] - xml.tag! 'chname', credit_card.name - xml.tag! 'issueno', credit_card.issue_number - - xml.tag! 'cvn' do - xml.tag! 'number', credit_card.verification_value - xml.tag! 'presind', credit_card.verification_value? ? 1 : nil - end - end - - xml.tag! 'autosettle', 'flag' => auto_settle_flag(action) - xml.tag! 'sha1hash', sha1from("#{timestamp}.#{@options[:login]}.#{sanitize_order_id(options[:order_id])}.#{amount(money)}.#{options[:currency] || currency(money)}.#{credit_card.number}") - xml.tag! 'comments' do - xml.tag! 'comment', options[:description], 'id' => 1 - xml.tag! 'comment', 'id' => 2 - end - - billing_address = options[:billing_address] || options[:address] || {} - shipping_address = options[:shipping_address] || {} - - xml.tag! 'tssinfo' do + def build_void_request(authorization, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'void' do + add_merchant_details(xml, options) + add_transaction_identifiers(xml, authorization, options) + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], options[:order_id]) + end + xml.target! + end + + def add_address_and_customer_info(xml, options) + billing_address = options[:billing_address] || options[:address] + shipping_address = options[:shipping_address] + + return unless billing_address || shipping_address || options[:customer] || options[:invoice] || options[:ip] + + xml.tag! 'tssinfo' do + xml.tag! 'custnum', options[:customer] if options[:customer] + xml.tag! 'prodid', options[:invoice] if options[:invoice] + xml.tag! 'custipaddress', options[:ip] if options[:ip] + + if billing_address xml.tag! 'address', 'type' => 'billing' do - xml.tag! 'code', billing_address[:zip] + xml.tag! 'code', avs_input_code( billing_address ) xml.tag! 'country', billing_address[:country] end + end + if shipping_address xml.tag! 'address', 'type' => 'shipping' do xml.tag! 'code', shipping_address[:zip] xml.tag! 'country', shipping_address[:country] end - - xml.tag! 'custnum', options[:customer] - - xml.tag! 'prodid', options[:invoice] - xml.tag! 'varref' end end + end - xml.target! + def add_merchant_details(xml, options) + xml.tag! 'merchantid', @options[:login] + if options[:account] || @options[:account] + xml.tag! 'account', options[:account] || @options[:account] + end end + + def add_transaction_identifiers(xml, authorization, options) + options[:order_id], pasref, authcode = authorization.split(';') + xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + xml.tag! 'pasref', pasref + xml.tag! 'authcode', authcode + end + + def add_comments(xml, options) + return unless options[:description] + xml.tag! 'comments' do + xml.tag! 'comment', options[:description], 'id' => 1 + end + end + + def add_amount(xml, money, options) + xml.tag! 'amount', amount(money), 'currency' => options[:currency] || currency(money) + end + + def add_card(xml, credit_card) + xml.tag! 'card' do + xml.tag! 'number', credit_card.number + xml.tag! 'expdate', expiry_date(credit_card) + xml.tag! 'chname', credit_card.name + xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s] + xml.tag! 'issueno', credit_card.issue_number + xml.tag! 'cvn' do + xml.tag! 'number', credit_card.verification_value + xml.tag! 'presind', (options['presind'] || (credit_card.verification_value? ? 1 : nil)) + end + end + end + + def avs_input_code(address) + address.values_at(:zip, :address1).map{ |v| extract_digits(v) }.join('|') + end + + def extract_digits(string) + return "" if string.nil? + string.gsub(/[\D]/,'') + end + + def stringify_values(values) + string = "" + (0..5).each do |i| + string << "#{values[i]}" + string << "." unless i == 5 + end + string + end + def new_timestamp + Time.now.strftime('%Y%m%d%H%M%S') + end + + def add_signed_digest(xml, *values) + string = stringify_values(values) + xml.tag! 'sha1hash', sha1from(string) + end + def auto_settle_flag(action) action == :authorization ? '0' : '1' end - + def expiry_date(credit_card) "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" end - + def sha1from(string) Digest::SHA1.hexdigest("#{Digest::SHA1.hexdigest(string)}.#{@options[:password]}") end - + def normalize(field) case field when "true" then true when "false" then false when "" then nil when "null" then nil else field - end + end end - + def message_from(response) message = nil - case response[:result] + case response[:result] when "00" message = SUCCESS when "101" message = response[:message] when "102", "103" @@ -182,17 +304,19 @@ when /^2[0-9][0-9]/ message = BANK_ERROR when /^3[0-9][0-9]/ message = REALEX_ERROR when /^5[0-9][0-9]/ + message = response[:message] + when "600", "601", "603" message = ERROR when "666" message = CLIENT_DEACTIVATED else message = DECLINED - end + end end - + def sanitize_order_id(order_id) order_id.to_s.gsub(/[^a-zA-Z0-9\-_]/, '') end end end \ No newline at end of file