class ActiveModel::Validations::CreditCardFieldsValidator

Constants

ERROR_TYPES
PROVIDERS

Attributes

cc_cvv[RW]
cc_first_name[RW]
cc_last_name[RW]
cc_month[RW]
cc_number[RW]
cc_owner[RW]
cc_providers[RW]
cc_type[RW]
cc_year[RW]
custom_messages[RW]
options[RW]
using_owner[RW]

Public Class Methods

new(options={}) click to toggle source
# File lib/validate_credit_card_fields.rb, line 30
def initialize(options={})
  @options = options
  @custom_messages = {}
  @cc_number = init_option(:number, :cc_number)
  @cc_cvv = init_option(:cvv, :cc_cvv)
  @cc_month = init_option(:month, :cc_month)
  @cc_year = init_option(:year, :cc_year)
  @cc_providers = options[:providers]
  @using_owner = !options[:owner].nil? || options[:first_name].nil? || options[:last_name].nil?
  if @using_owner
    @cc_owner = init_option(:owner, :cc_owner)
  else
    @cc_first_name = init_option(:first_name, :cc_first_name)
    @cc_last_name = init_option(:last_name, :cc_last_name)
  end
end

Public Instance Methods

init_option(key, default) click to toggle source
# File lib/validate_credit_card_fields.rb, line 47
def init_option(key, default)
  if options[key].is_a? Hash
    init_option_from_hash options[key], default
  else
    options[key] || default
  end
end
init_option_from_hash(hash, default) click to toggle source
# File lib/validate_credit_card_fields.rb, line 55
def init_option_from_hash(hash, default)
  field_name = hash[:field] || default
  custom_messages[field_name] = hash.select{ |k,_| ERROR_TYPES.include? k }
  field_name
end
validate(record) click to toggle source
# File lib/validate_credit_card_fields.rb, line 61
def validate(record)
  @record = record
  check_fields_format
  @cc_type = get_cc_type
  validate_fields_presence
  validate_cc_number
  validate_cc_cvv
  validate_cc_month
  validate_cc_year
  validate_cc_expiry_date
end

Private Instance Methods

add_error(field, message) click to toggle source
# File lib/validate_credit_card_fields.rb, line 141
def add_error(field, message)
  @record.errors.add(field, error_message(field, message)) if @record.errors[field].blank?
end
check_fields_format() click to toggle source
# File lib/validate_credit_card_fields.rb, line 161
def check_fields_format
  invalid_attr = validated_fields.find do |attr|
    !@record.public_send(attr).is_a?(NilClass) && !@record.public_send(attr).is_a?(String)
  end
  if invalid_attr
    raise CCTypeError, "#{invalid_attr} is a #{@record.public_send(invalid_attr).class}, String expected."
  end
end
error_message(field, message) click to toggle source
# File lib/validate_credit_card_fields.rb, line 153
def error_message(field, message)
  custom_messages[field].try(:[], message) || translate_error(message)
end
get_cc_number() click to toggle source
# File lib/validate_credit_card_fields.rb, line 149
def get_cc_number
  @record.public_send(cc_number).try(:delete, ' ')
end
get_cc_type() click to toggle source
# File lib/validate_credit_card_fields.rb, line 145
def get_cc_type
  PROVIDERS.find{ |_, regex| regex.match(get_cc_number) }.try(:first)
end
luhn_algorithm_valid?() click to toggle source
# File lib/validate_credit_card_fields.rb, line 129
def luhn_algorithm_valid?
  return true if @cc_type == :china_union
  s1 = s2 = 0
  get_cc_number.to_s.reverse.chars.each_slice(2) do |odd, even|
    s1 += odd.to_i
    double = even.to_i * 2
    double -= 9 if double >= 10
    s2 += double
  end
  (s1 + s2) % 10 == 0
end
translate_error(error) click to toggle source
# File lib/validate_credit_card_fields.rb, line 157
def translate_error error
  ::I18n.t("errors.messages.#{error}")
end
validate_cc_cvv() click to toggle source
# File lib/validate_credit_card_fields.rb, line 94
def validate_cc_cvv
  length = (@cc_type == :amex) ? 4 : 3
  unless !@cc_type.nil? && (/\A\d{#{length}}\z/).match(@record.public_send(cc_cvv))
    add_error(cc_cvv, :invalid)
  end
end
validate_cc_expiry_date() click to toggle source
# File lib/validate_credit_card_fields.rb, line 113
def validate_cc_expiry_date
  if (@record.errors.messages.keys & [cc_year, cc_month]).empty?
    year = "20#{@record.public_send(cc_year)}".to_i
    month = @record.public_send(cc_month).to_i
    date = Date.new(year, month).next_month.prev_day
    if date < Date.today
      field = if date.year < Date.today.year
                cc_year
              else
                cc_month
              end
      add_error(field, :invalid)
    end
  end
end
validate_cc_month() click to toggle source
# File lib/validate_credit_card_fields.rb, line 101
def validate_cc_month
  unless (/\A\d{2}\z/).match("%02d" % @record.public_send(cc_month).to_i) && @record.public_send(cc_month).to_i.between?(1, 12)
    add_error(cc_month, :invalid)
  end
end
validate_cc_number() click to toggle source
# File lib/validate_credit_card_fields.rb, line 85
def validate_cc_number
  err = if @cc_type.nil? || !luhn_algorithm_valid?
          :invalid
        elsif !cc_providers.blank? && !cc_providers.include?(@cc_type)
          :inclusion
        end
  add_error(cc_number, err) if err
end
validate_cc_year() click to toggle source
# File lib/validate_credit_card_fields.rb, line 107
def validate_cc_year
  unless (/\A\d{2}\z/).match(@record.public_send(cc_year)) && (@record.public_send(cc_year).to_i >= Date.today.year-2000)
    add_error(cc_year, :invalid)
  end
end
validate_fields_presence() click to toggle source
# File lib/validate_credit_card_fields.rb, line 79
def validate_fields_presence
  validated_fields.each do |field|
    add_error(field, :blank) if @record.public_send(field).blank?
  end
end
validated_fields() click to toggle source
# File lib/validate_credit_card_fields.rb, line 75
def validated_fields
  [cc_number, cc_cvv, cc_month, cc_year] + (using_owner ? [cc_owner] : [cc_first_name, cc_last_name])
end