module EWS # :nodoc: module Transaction # :nodoc: module Validator @@valid_lengths = { :authorization_num => 8, :cardholder_name => 30, :cc_number => 19, :cc_expiry => 4, :cavv => 40, :client_email => 30, :client_ip => 15, :customer_ref => 20, :gateway_id => 10, :pan => 39, :password => 30, :reference_3 => 30, :reference_no => 20, :tax1_number => 20, :tax2_number => 20, :track1 => 79, :track2 => 40, :transaction_type => 2, :cc_verification_str1 => 40, :cc_verification_str2 => 4, :xid => 40, :zip_code => 10 }.freeze unless defined?(@@valid_lengths) def valid? @errors = {} append_error(:transaction_type, "transaction_type must be supplied") if self.transaction_type.blank? # need to authenticate append_error(:gateway_id, "gateway_id must be supplied") if self.gateway_id.blank? append_error(:password, "password must be supplied") if self.password.blank? validate_lengths if self.transaction_type == "CR" append_error(:transaction_tag, "transaction_tag must be supplied") if self.transaction_tag.to_i < 1 elsif self.transaction_type == "36" validate_referenced_void elsif %w(80 81).include?(self.transaction_type) # do nothing, no validation required elsif %w(50 54).include?(self.transaction_type) validate_for_pan elsif !self.transaction_tag.blank? validate_for_transaction_tag elsif !self.cc_number.blank? validate_for_cc_number elsif !self.track1.blank? validate_for_track1 elsif !self.track2.blank? validate_for_track2 else append_error(:base, "One of the following must be supplied: cc_number, track1, track2 or transaction_tag.") end # ensure we've been given valid amounts append_error(:amount, "invalid amount supplied") unless valid_amount?(self.amount) append_error(:surcharge_amount, "invalid surcharge_amount supplied") unless valid_amount?(self.surcharge_amount) append_error(:tax1_amount, "invalid tax1_amount supplied") unless valid_amount?(self.tax1_amount) append_error(:tax2_amount, "invalid tax2_amount supplied") unless valid_amount?(self.tax2_amount) # ensure our amounts are within range append_error(:amount, "amount must be between 0.00 and 99999.99") unless amount_in_range?(self.amount) append_error(:surcharge_amount, "surcharge_amount must be between 0.00 and 99999.99") unless amount_in_range?(self.surcharge_amount) append_error(:tax1_amount, "tax1_amount must be between 0.00 and 99999.99") unless amount_in_range?(self.tax1_amount) append_error(:tax2_amount, "tax2_amount must be between 0.00 and 99999.99") unless amount_in_range?(self.tax2_amount) # ensure our credit card information is valid append_error(:cc_number, "invalid cc_number supplied") unless valid_card_number? append_error(:cc_expiry, "invalid cc_expiry supplied") unless valid_expiry_date? @errors.empty? end private def validate_lengths @@valid_lengths.each do |k,len| value = self.send k append_error(k, "#{k.to_s} is too long. Maximum allowed length is #{len} characters") unless value.nil? or (value.length <= len) end end def valid_amount?(amount) return true if amount.blank? ((amount.class == Float) or (amount.class == Fixnum) or !amount.match(/[^0-9.]/)) end def amount_in_range?(amount) return true if amount.blank? return ((amount.to_f <= 99999.99) and (amount.to_f >= 0.0)) end def valid_card_number? return true if self.cc_number.blank? # do a mod10 check weight = 1 card_number = self.cc_number.scan(/./).map(&:to_i) result = card_number.reverse[1..-1].inject(0) do |sum, num| weight = 1 + weight%2 digit = num * weight sum += (digit / 10) + (digit % 10) end card_number.last == (10 - result % 10 ) % 10 end # date should be... # - not blank # - 4 digits # - MMYY format # - not in the past def valid_expiry_date? return true if self.cc_expiry.blank? # check format return false unless self.cc_expiry.match(/^\d{4}$/) # check date is not in past year, month = self.cc_expiry[2..3].to_i, self.cc_expiry[0..1].to_i year += (year > 79) ? 1900 : 2000 # CC is still considered valid during the month of expiry, # so just compare year and month, ignoring the rest. now = DateTime.now return ((1..12) === month) && DateTime.new(year, month) >= DateTime.new(now.year, now.month) end def append_error(key, message) if @errors[key].nil? @errors[key] = message else # otherwise convert into an array of errors current_val = @errors[key] if current_val.is_a?(String) @errors[key] = [current_val, message] else @errors[key] << message end end end # validate presence of mandatory fields when cc_number present def validate_for_cc_number tt = self.transaction_type.to_i # mandatory: transaction_type must != (30, 31, 32, 34, 35) append_error(:cc_number, "cc_number must not be set for tagged transactions") if [30,31,32,33,34,35].include?(tt) append_error(:cc_number, "cc_number must not be set for debit transactions") if [50,54].include?(tt) # amount, cardholder_name always mandaory mandatory = [:amount, :cardholder_name, :cc_number, :cc_expiry] # reference_no mandatory for 60 mandatory << :reference_no if tt == 60 # auth_number mandatory for (02, 03, 11, 12, 13) mandatory << :authorization_num if [02, 03, 11, 12, 13].include?(tt) check_mandatory(mandatory) end def validate_for_transaction_tag tt = self.transaction_type # mandatory: transaction_type must == (30, 31, 32, 33, 34, 35, 37, CR) append_error(:transaction_tag, "transaction_tag must only be set for tagged transactions") unless %w(30 31 32 33 34 35 37 CR).include?(tt) # transaction_tag, auth_num & amount mandatory mandatory = [:transaction_tag] mandatory << :authorization_num unless tt == 'CR' mandatory << :amount unless %w(CR 37).include?(tt) # ensure no cc details sent append_error(:cc_number, "do not set cc_number for tagged transactions") unless self.cc_number.blank? append_error(:cc_expiry, "do not set cc_expiry for tagged transactions") unless self.cc_expiry.blank? append_error(:cardholder_name, "do not set cardholder_name for tagged transactions") unless self.cardholder_name.blank? check_mandatory(mandatory.flatten) end def validate_for_track1 tt = self.transaction_type.to_i # mandatory: transaction_type must != (30, 31, 32, 34, 35) append_error(:track1, "track1 must not be set for tagged transactions") if [30,31,32,33,34,35].include?(tt) append_error(:track1, "track1 must not be set for debit transactions") if [50,54].include?(tt) # amount mandatory for all mandatory = [:track1, :amount] # reference_no mandatory for 60 mandatory << :reference_no if tt == 60 # auth_number mandatory for (02, 03, 11, 12, 13) mandatory << :authorization_num if [02, 03, 11, 12, 13].include?(tt) check_mandatory(mandatory) end def validate_for_track2 tt = self.transaction_type.to_i # mandatory: transaction_type must != (30, 31, 32, 34, 35, 50, 54) append_error(:track2, "track2 must not be set for tagged transactions") if [30,31,32,33,34,35].include?(tt) append_error(:track2, "track2 must not be set for debit transactions") if [50,54].include?(tt) # track2, expiry_date, cardholder_name, amount mandatory mandatory = [:track2, :cardholder_name, :amount] # auth_number mandatory for (02, 03, 11, 12, 13) mandatory << :authorization_num if [02, 03, 11, 12, 13].include?(tt) check_mandatory(mandatory) end def validate_for_pan tt = self.transaction_type.to_i # mandatory: transaction_type must == (50, 54) append_error(:pan, "pan must not be set for non-debit transactions") unless [50,54].include?(tt) # track2, expiry_date, cardholder_name, amount mandatory mandatory = [:pan, :cardholder_name, :amount] check_mandatory(mandatory) end def validate_referenced_void [:cc_number, :pan, :track1, :track2, :transaction_tag].each do |attr_name| append_error(attr_name, "#{attr_name} must not be set for referenced_void transactions") unless self.send(attr_name).nil? end check_mandatory([:reference_no, :customer_ref, :amount]) end def check_mandatory(mandatory) mandatory.each do |key| append_error(key, "#{key} must be supplied") if self.send(key).blank? end end end end end