Skip to content

Instantly share code, notes, and snippets.

@pamio
Created November 14, 2014 11:35
Show Gist options
  • Save pamio/db2703f6ff6368cde94a to your computer and use it in GitHub Desktop.
Save pamio/db2703f6ff6368cde94a to your computer and use it in GitHub Desktop.
Credit Card Verification
class CreditCard
require 'date'
SUPPORTED_CARD_BRANDS = {
:MASTERCARD => "mastercard",
:AMEX => "american_express",
:DINNERS => "diners_club",
:DISCOVER => "discover",
:MAESTRO => "maestro",
:VISA => "visa",
:JCB => "jcb"
}
def initialize(card_num, month, year, cvv)
@card = card_num
@cvv = cvv
@month = month
@year = year
end
def validate
errors = validate_essential_attributes + validate_verification_value
errors_hash(
errors +
validate_card_brand_and_number
)
end
def errors_hash(array)
array.inject({}) do |hash, (attribute, error)|
(hash[attribute] ||= []) << error
hash
end
end
def expiry_date
ExpiryDate.new(@month, @year)
end
def expired?
expiry_date.expired?
end
def brand
type
end
def valid_card_verification_value?(cvv, type)
cvv.to_s =~ /^\d{#{card_verification_value_length(type)}}$/
end
def card_verification_value_length(type)
type == 'american_express' ? 4 : 3
end
def valid_month?(month)
(1..12).include?(month.to_i)
end
def valid_expiry_year?(year)
(Time.now.year..Time.now.year + 20).include?(year.to_i)
end
def valid_start_year?(year)
((year.to_s =~ /^\d{4}$/) && (year.to_i > 1987))
end
# check sum of credit card number using luhn's alogorithm
def self.check_luhn(card_num)
card_num.gsub!(/[^0-9]/, "")
ss = card_num.reverse.split(//)
alternate = false
total = 0
ss.each do |c|
if alternate
total += double_it(c.to_i)
else
total += c.to_i
end
alternate = !alternate
end
(total % 10) == 0
end
# return credit card type if its one of the supported cards
def type
if @card =~ /^5[1-5][0-9]{14}$/
return SUPPORTED_CARD_BRANDS[:MASTERCARD]
elsif @card.match(/^4[0-9]{12}([0-9]{3})?$/)
return SUPPORTED_CARD_BRANDS[:VISA]
elsif @card.match(/^3[47][0-9]{13}$/)
return SUPPORTED_CARD_BRANDS[:AMEX]
elsif @card =~ /^3(0[0-5]|[68][0-9])[0-9]{11}$/
return SUPPORTED_CARD_BRANDS[:DINNERS]
elsif @card =~ /^6011[0-9]{12}$/
return SUPPORTED_CARD_BRANDS[:DISCOVER]
elsif @card =~ /^(3[0-9]{4}|2131|1800)[0-9]{11}$/
return SUPPORTED_CARD_BRANDS[:JCB]
elsif @card =~ /^(5[06-8]|6)[0-9]{10,17}$/
return SUPPORTED_CARD_BRANDS[:MAESTRO]
else
return nil
end
end
def valid_number?
valid = CreditCard::check_luhn(@card)
end
# check if valid
def valid?
errors = validate()
return false unless empty?(errors)
return true
end
def empty?(value)
case value
when nil
true
when Array, Hash
value.empty?
when String
value.strip.empty?
when Numeric
(value == 0)
else
false
end
end
private
def validate_essential_attributes
errors = []
if(empty?(@month) || empty?(@year))
errors << [:month, "is required"] if empty?(@month)
errors << [:year, "is required"] if empty?(@year)
else
errors << [:month, "is not a valid month"] if !valid_month?(@month)
if expired?
errors << [:year, "expired"]
else
errors << [:year, "is not a valid year"] if !valid_expiry_year?(@year)
end
end
errors
end
def validate_card_brand_and_number
errors = []
if empty?(@card)
errors << [:number, "is required"]
elsif !valid_number?
errors << [:number, "is not a valid credit card number"]
end
if errors.empty?
errors << [:brand, "card not supported"] if type.nil?
end
errors
end
def validate_verification_value
errors = []
if @cvv
unless valid_card_verification_value?(@cvv, self.type)
errors << [:cvv, "should be #{card_verification_value_length(self.type)} digits"]
end
else
errors << [:cvv, "is required"]
end
errors
end
def self.double_it(i)
i = i * 2
if i > 9
i = i % 10 + 1
end
i
end
class ExpiryDate
attr_reader :month, :year
def initialize(month, year)
@month = month.to_i
@year = year.to_i
end
def expired?
Time.now.utc > expiration
end
def expiration
begin
Time.utc(year, month, month_days, 23, 59, 59)
rescue ArgumentError
Time.at(0).utc
end
end
private
def month_days
mdays = [nil,31,28,31,30,31,30,31,31,30,31,30,31]
mdays[2] = 29 if Date.leap?(year)
mdays[month]
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment