Here's my solution.
require 'enumerator'
class CardProcessor
CARDS = {'visa' => {:length => [13,16], :begin => [4]},
'amex' => {:length => [15], :begin => [34,37]},
'discover' => {:length => [16], :begin => [6011]},
'mastercard' => {:length => [16], :begin => (51..55)},
'jcb' => {:length => [16], :begin => (3528..3589)},
'diners club' => {:length => [14], :begin =>
[(3000..3029).to_a, (3040..3059).to_a, 36, (3815..3889).to_a,
389].flatten}
}
def initialize(name, number)
@name = name.downcase
@number = number.gsub(/\D/,'')
end
def luhn_valid?
a = ''
@number.split('').reverse.each_slice(2){ |leave, double| a <<
leave << (double.to_i * 2).to_s }
a.split('').inject(0){|s,v| s + v.to_i } % 10 == 0
end
def length_valid?
CARDS[@name][:length].include? @number.size
end
def beginning_valid?
@number =~ /^#{CARDS[@name][:begin].to_a.join('|')}/
end
def valid?
beginning_valid? && length_valid? && luhn_valid?
end
def self.cards
CARDS.keys
end
end
if __FILE__ == $0
if ARGV.empty?
puts "Usage ruby #{File.basename($0)} <cardnumber>"
exit 0
end
number = ARGV.join
if CardProcessor.new('', number).luhn_valid?
puts "Your card appears to be a valid card."
result = CardProcessor.cards.map {|card| card if
CardProcessor.new(card, number).valid? }.compact
puts "Vendor: #{(result.empty? ? 'unknown' :
result.first).capitalize}"
else
puts "Your card doesn't appear to be valid."
end
end