# Checking Credit Cards (#122)

**URL:** <https://www.ruby-forum.com/t/checking-credit-cards-122/93097>\
**Category:** Ruby\
**Created:** [April 27, 2007, 2:00pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097 "2007-04-27T14:00:30Z")\
**Posts on this page:** 19\
**Page:** 3

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 5:55pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/41 "2007-04-29T17:55:13Z")

</div>

This quiz reminded me of my days in credit card processing. The  
weighted checksum for routing numbers is more interesting, but the  
sort-of-two-pass aspect of the Luhn algorithm is a great stumbling  
block. My solution follows. You’ll notice I liked your ARGV.join  
trick for the input.

class Array  
def cycle!  
push(shift)  
end  
end

class CCNum \< String  
PATTERNS = {  
‘AMEX’ =\> { :start =\> [‘34’, ‘37’], :length =\> 15 },  
‘Discover’ =\> { :start =\> [‘6011’, ‘65’], :length =\> 16 },  
‘MasterCard’ =\> { :start =\> (51…55).to\_a.collect { |n|  
n.to\_s }, :length =\> 16 },  
‘Visa’ =\> { :start =\> ‘4’, :length =\> [13, 16] },  
}.freeze

def initialize(\*args)  
super  
gsub!(/\D/, ‘’)  
@factors = [1,2]  
@factors.cycle! if (length % 2) == 1  
end

def type  
return @type if @type  
PATTERNS.each do |name, pat|  
@type = name if [pat[:start]].flatten.any? { |s| match /  
^#{s}/ } and [pat[:length]].flatten.any? { |l| length == l }  
end  
@type ||= ‘Unknown’  
end

def luhn\_sum  
@luhn\_sum ||= split(’’).inject(0) do |sum, digit|  
@factors.cycle!  
sum += (digit.to\_i \* @factors.first).to\_s.split(’’).inject(0) { |  
s,d| s += d.to\_i }  
end  
end

def luhn\_valid?  
(luhn\_sum % 10) == 0  
end  
end

card\_num = CCNum.new(ARGV.join)  
puts “#{card\_num} is a(n) #{card\_num.luhn\_valid? ? ‘V’ : ‘Inv’ }alid  
#{card\_num.type}”

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 4:43pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/42 "2007-04-29T16:43:07Z")

</div>

Here is my solution, including unit tests. I’m pretty pleased with my  
luhn check.

require ‘enumerator’

class CardType \< Struct.new(:name, :pattern, :lengths)  
def match(cc)  
(cc =~ pattern) and lengths.include?(cc.length)  
end

def to\_s  
name  
end  
end

class CardValidator  
@types = [  
CardType.new(‘AMEX’, /^(34|37)/, [15]),  
CardType.new(‘Discover’, /^6011/, [16]),  
CardType.new(‘MasterCard’, /^5[1-5]/, [16]),  
CardType.new(‘Visa’, /^4/, [13,16])  
]

def self.card\_type(cc)  
@types.find {|type| type.match(cc) }  
end

def self.luhn\_check(cc)  
# I like functional-style code (though this may be a bit over the  
top)  
(cc.split(’’).reverse.enum\_for(:each\_slice, 2).inject(’’) do |s, (a,  
b)|  
s \<\< a + (b.to\_i \* 2).to\_s  
end.split(’’).inject(0) {|sum, n| sum + n.to\_i}) % 10 == 0  
end  
end

require ‘test/unit’

class CardValidatorTest \< Test::Unit::TestCase  
def test\_card\_type  
assert\_equal(‘AMEX’,  
CardValidator.card\_type(‘341122567979797’).name)  
assert\_equal(‘AMEX’,  
CardValidator.card\_type(‘371122567979797’).name)  
assert\_equal(‘Discover’,  
CardValidator.card\_type(‘6011123456781122’).name)  
assert\_equal(‘MasterCard’,  
CardValidator.card\_type(‘5115666677779999’).name)  
assert\_equal(‘MasterCard’,  
CardValidator.card\_type(‘5315666677779999’).name)  
assert\_equal(‘Visa’,  
CardValidator.card\_type(‘4408041234567893’).name)  
assert\_equal(‘Visa’,  
CardValidator.card\_type(‘4417123456789112’).name)  
assert\_equal(‘Visa’, CardValidator.card\_type(‘4417123456789’).name)  
assert\_nil(CardValidator.card\_type(‘3411225679797973’))  
assert\_nil(CardValidator.card\_type(‘601112345678112’))  
assert\_nil(CardValidator.card\_type(‘51156666777799’))  
assert\_nil(CardValidator.card\_type(‘5615666677779989’))  
assert\_nil(CardValidator.card\_type(‘1111222233334444’))  
assert\_nil(CardValidator.card\_type(‘44171234567898’))  
end

def test\_luhn\_check  
assert(CardValidator.luhn\_check(‘1111222233334444’))  
assert(CardValidator.luhn\_check(‘4408041234567893’))  
assert(!CardValidator.luhn\_check(‘4417123456789112’))  
assert(!CardValidator.luhn\_check(‘6011484800032882’))  
end  
end

if $0 == **FILE**  
abort(“Usage: #$0 or -t to run unit tests”) if  
ARGV.length \< 1  
if not ARGV.delete(’-t’)  
Test::Unit.run = true

```
cc = ARGV.join.gsub(/\s*/, '')

type = CardValidator.card_type(cc)
puts "Card type is: #{type ? type : 'Unknown'}"
puts "The card is #{CardValidator.luhn_check(cc) ? 'Valid' : 

```

‘Invalid’}"  
end  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 7:05pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/43 "2007-04-29T19:05:17Z")

</div>

Hi all,

I took the library writing approach and since I have recently been using  
Austin’s excellent mime-types library, I took a similar approach with  
CreditCard Types. That is, a global registration of types that are  
described in a here document.

If there is interest I’ll polish it up a bit and release it as a gem.

Comments are welcome.

enjoy,

-jeremy

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 7:41pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/44 "2007-04-29T19:41:20Z")

</div>

This is my first rubyquiz. Here is my solution.

class CreditCard  
class CardType \< Struct.new(:name, :regex, :accepted\_lengths)  
def valid\_length?(length)  
if accepted\_lengths.is\_a?(Array)  
return accepted\_lengths.include?(length)  
else  
return accepted\_lengths == length  
end  
end  
end

CARD\_TYPES = [CardType.new(‘AMEX’, /^3[47]/, 15),  
CardType.new(‘Discover’, /^6011/, 16),  
CardType.new(‘MasterCard’, /^5[1-5]/, 16),  
CardType.new(‘Visa’, /^4/, [13, 16]),  
CardType.new(‘Unknown’, /.\*/, 0)]

def initialize(number)  
@number = number  
@card\_type = CARD\_TYPES.find {|t| @number =~ t.regex }  
end

def card\_type  
@card\_type.name  
end

def valid?  
return false unless @card\_type.valid\_length?(@number.length)  
numbers = @number.split(//).collect {|x| x.to\_i}  
i = numbers.length - 2  
while i \>= 0  
numbers[i] \*= 2  
i -= 2  
end  
numbers = numbers.to\_s.split(//)  
sum = 0; numbers.each {|x| sum += x.to\_i}  
sum % 10 == 0  
end  
end

abort “Usage: #{$0} card\_number […]” if ARGV.empty?  
ARGV.each do |card\_number|  
c = CreditCard.new(card\_number)  
out = "#{card\_number}: "  
out += (c.valid? ? "Valid " : "Invalid ")  
out += “#{c.card\_type}”  
puts out  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 7:46pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/45 "2007-04-29T19:46:17Z")

</div>

Here’s what I came up with. I hope it’s as short and to the point as I  
think it is.

Chris

#!/usr/local/bin/ruby

class CreditCard

TYPES = {  
:visa =\> {:length =\> [13, 16], :start =\> [4]},  
:discover =\> {:length =\> [16], :start =\> [6011]},  
:mastercard =\> {:length =\> [16], :start =\> 51…55},  
:amex =\> {:length =\> [15], :start =\> [34, 37]}  
}

def initialize(number)  
@number = number.gsub(/\D/,’’)  
end

def valid?  
adjusted\_numbers = ‘’  
@number.reverse.split(’’).each\_with\_index do |x, i|  
adjusted\_numbers \<\< (i % 2 == 0 ? x : (x.to\_i \* 2).to\_s)  
end  
adjusted\_numbers.split(’’).inject(0) {|sum, x| sum += x.to\_i} % 10  
== 0  
end

def card\_type  
TYPES.each do |type, criteria|  
if criteria[:start].any? {|n|  
@number.match(Regexp.compile(’^’+n.to\_s))}  
if criteria[:length].include? @number.length  
return type  
end  
end  
end  
:unknown  
end

end

if **FILE** == $0  
test\_card = CreditCard.new(ARGV.join(’’))  
puts “Card type: #{test\_card.card\_type}”  
print test\_card.valid? ? “Passes” : “Fails”  
puts " the Luhn algorithm."  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 8:17pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/46 "2007-04-29T20:17:21Z")

</div>

Thanks for another simple but fun quiz! James, thanks for your  
critiques on last week’s quiz; they were educational.

I did two versions of the Luhn algorithm. The first one seems more  
readable to me (though I’m a Ruby nuby), but the second was fun to  
put together.

Comments are welcome.

-Mark

# 

# Ruby Q. #122: Credit card validation

# 

require ‘enumerator’

class Array  
def sum(initial=0)  
inject(initial) { |total, elem| total + elem }  
end

# Compute the pairwise product of two arrays.

# That is: result[i] = self[i] \* other[i] for i in 0…self.length

def pairwise\_product(other)  
result = []  
each\_index {|i| result \<\< self[i]\*other[i] }  
return result  
end  
end

class Integer  
def digits  
self.to\_s.split(’’).map { |digit| digit.to\_i }  
end  
end

class CreditCard  
@@types = [[“AMEX”, /^3[47]\d{13}$/],  
[“Discover”, /^6011\d{12}$/],  
[“MasterCard”, /^5[1-5]\d{14}$/],  
[“Visa”, /^4\d{12}(\d{3})?$/],  
[“Unknown”, //]]  
attr\_reader :type

def initialize(str)  
num = str.delete(" ")

```
 # Disallow card "numbers" with non-digits
 if num =~ /\D/
   @type = "Unknown"
   @valid = false
   return
 end

 # See which of the patterns match the string
 @type = @@types.find {|name, regexp| num =~ regexp }[0]

 # See if the card number is valid according to the Luhn algorithm
 @valid = num.reverse.split('').enum_slice(2).inject(0) do
   |sum, (odd, even)|
   sum + odd.to_i + (even.to_i*2).digits.sum
 end % 10 == 0

```

=begin  
#  
# This works, too. But it seems awfully long and complicated.  
#  
# The idea is to combine the digits of the credit card number with  
# a sequence of 1’s and 2’s so that every other digit gets doubled.  
# Then sum up the digits of each product.  
#  
# BTW, the “[1,2]\*num.length” construct builds an array that’s  
twice  
# as long as necessary. The entire array only needs num.length  
# elements, but having more is OK. This was the easy way of making  
# sure it was big enough.  
#  
@valid = num.reverse.to\_i.digits.pairwise\_product([1,2]  
\*num.length).  
map{|x| x.digits.sum}.sum % 10 == 0  
=end  
end

def valid?  
@valid  
end  
end

if **FILE** == $0  
cc = CreditCard.new(ARGV.join)  
print cc.valid? ? “Valid” : “Invalid”, " #{cc.type}\n"  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 8:40pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/47 "2007-04-29T20:40:26Z")

</div>

This is my first quiz submission. The only testing I did was running  
the  
cards I had in my wallet. It seems to work. I’m sure there’s a much  
better  
way to implement this, but here it is:

#card\_check.rb

def check\_type(number)  
#Returns a string representing the type of card if the length and  
leading

# digits are valid. Otherwise returns “Unknown”.

valid = {  
/^(34|37)/ =\> [15,“AMEX”],  
/^6011/ =\> [16,“Discover”],  
/^(51|52|53|54|55)/ =\> [16,“MasterCard”],  
/^4/ =\> [13,16,“Visa”]  
}  
number.gsub!(/ /,"")  
valid.each\_key do |i|  
if number =~ i  
return valid[i][-1] if valid[i].include? number.length  
end  
end  
return “Unknown”  
end

def luhn(number)

# Returns “valid” if the number passes the Luhn algorihm criteria.

Returns

# “invalid” if the algorithm fails.

number = number.gsub(/ /,"").split(//).reverse  
new\_number = “”  
number.each\_index do |i|  
new\_number \<\< (number[i].to\_i\*2).to\_s if (i+1) % 2 == 0  
new\_number \<\< number[i] if (i+1) % 2 == 1  
end  
new\_number = new\_number.split(//)  
sum = 0  
new\_number.each\_index { |i| sum += new\_number[i].to\_i }  
return “valid” if sum % 10 == 0 unless number.length == 0  
return “invalid”  
end

def validate\_card(number)  
puts “Type: #{check\_type(number)}”  
puts “Status: #{luhn(number)}”  
end

validate\_card(ARGV.join("")) if **FILE** == $0

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 9:39pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/48 "2007-04-29T21:39:31Z")

</div>

Here is my solution a bit schoolboyish ☹

## BEGIN : quiz122.rb

#!/usr/bin/ruby

def is\_luhn? cardno

digits = cardno.split(//).reverse!

sum = ‘’  
digits.each\_index do |i|  
if i%2 == 0  
sum \<\< digits[i]  
else  
sum \<\< (2 \* digits[i].to\_i).to\_s  
end

end  
s1 = 0  
sum.split(//).inject(s1) { |s1, v| s1 += v.to\_i }  
if s1 % 10 == 0  
return “Valid”  
else  
return “InValid”  
end

end

if **FILE** == $0:

abort(“Usage: ruby quiz122.rb #CARDNO”) if ARGV.length != 1

cardno = ARGV[0].gsub(/[^0-9]/, ‘’)

is\_luhn? cardno

print "card number "#{cardno}" is "  
case cardno.length  
when 13  
if cardno.match(/^4/)  
print "Visa : "  
else  
print "Unknown : "  
end  
puts is\_luhn?(cardno)

when 14  
if  
cardno.match(/(^30[0-2][0-9])|(^30[4-5][0-9])|(^36)|(^38(1[5-9]|[2-9]))/)  
print "Diners : "  
else  
print "Unknown : "  
end

when 15  
if cardno.match(/^3(4|7)/)  
print "Amex : "  
else  
print "Unknown : "  
end

when 16  
if cardno.match(/^6011/)  
print "Discover : "  
elsif cardno.match(/^4/)  
print "Visa : "  
elsif cardno.match(/^5[1-5]/)  
print "MasterCard : "  
elsif cardno.match(/^35(2[8-9]|[3-8][0-9])/)  
print "JCB : "  
else  
print "Unknown : "  
end

else  
print "Unknown : "

end

puts is\_luhn?(cardno) + " CC "

## end

On 4/27/07, Ruby Q. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> ```
> | MasterCard | 51-55 | 16 |
> 2. Sum all doubled and untouched digits in the number
> 
> ```
> 
> Let’s try one more, 4417 1234 5678 9112:  
> algorithm.

–  
à¤…à¤­à¤¿à¤œà¥€à¤¤

[written in [http://www.paahijen.com/scratchpad](http://www.paahijen.com/scratchpad) ]

[[http://www.paahijen.com](http://www.paahijen.com) ]

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 30, 2007, 6:26am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/49 "2007-04-30T06:26:07Z")

</div>

On Apr 27, 6:59 pm, Ruby Q. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone  
> on Ruby T. follow the discussion. Please reply to the original quiz message,  
> if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-  
=-=-=-=-=

> ```
> | Card Type | Begins With | Number Length |
> 
> ```
> 
> All of these card types also generate numbers such that they can be validated by  
> Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
> 
> That card is not valid.
> 
> This week’s Ruby Q. is to write a program that accepts a credit card number as  
> a command-line argument. The program should print the card’s type (or Unknown)  
> as well a Valid/Invalid indication of whether or not the card passes the Luhn  
> algorithm.

Here is my solution.  
#!/usr/bin/ruby

credit\_card\_number = ARGV.join

case  
when (credit\_card\_number=~/^(34|37)\d{13}$/): print 'AMEX ’  
when (credit\_card\_number=~/^6011\d{12}$/): print 'Discover ’  
when (credit\_card\_number=~/^5[1-5]\d{14}$/): print 'MasterCard ’  
when (credit\_card\_number=~/^4(\d{12}|\d{15})$/): print 'Visa ’  
else print 'Unknown ’  
end

i = 0  
luhl\_number = ‘’  
credit\_card\_number.reverse.each\_byte {|char|  
if (i%2==1) then  
char = (char.chr.to\_i \* 2).to\_s  
else  
char = char.chr  
end  
luhl\_number = char + luhl\_number  
i += 1  
}

sum\_total = 0

luhl\_number.each\_byte {|char|  
sum\_total += char.chr.to\_i  
}

if (sum\_total%10==0) then  
print “Valid\n”  
else  
print “Invalid\n”  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 29, 2007, 11:01pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/50 "2007-04-29T23:01:38Z")

</div>

anansi wrote:

> I’ll wait with my solution for a further hour 😉 but a comment to your  
> solution:  
> try out: ruby ccc.rb 5508 0412 3456 7893  
> it should show: Diners Club US & Canada or Mastercard  
> but shows just Mastercaards  
> Fixed, and resubmitting. Thanks for the lookout. I didn’t include that  
> initially because I didn’t think there would be overlap. I didn’t even  
> bother to look myself and see if that assumption was true.

# ccc.rb

# Checking Credit Cards

class String  
def begins\_with?(str)  
temp = self.slice(0…str.length)  
temp == str  
end  
end

class Array  
def collect\_with\_index  
self.each\_with\_index do |x, i|  
self[i] = yield(x, i)  
end  
end  
end

class CCNumber  
#This data was taken from

> **[Payment card number](https://en.wikipedia.org/wiki/Credit_card_number)**
>
> A payment card number, primary account number (PAN), or simply a card number, is the card identifier found on payment cards, such as credit cards and debit cards, as well as stored-value cards, gift cards and other similar cards. In some situations the card number is referred to as a bank card number. The card number is primarily a card identifier and may not directly identify the bank account number/s to which the card is/are linked by the issuing entity. The card number prefix identifies the i...

TYPES = [  
Hash[‘type’ =\> ‘American Express’, ‘key’ =\> [34,  
37], ‘length’ =\> [15]],  
Hash[‘type’ =\> ‘China Union Pay’, ‘key’ =\>  
(622126…622925).to\_a, ‘length’ =\> [16]],  
Hash[‘type’ =\> ‘Diners Club Carte Blanche’, ‘key’ =\>  
(300…305).to\_a, ‘length’ =\> [14]],  
Hash[‘type’ =\> ‘Diners Club International’, ‘key’ =\>  
[36], ‘length’ =\> [14]],  
Hash[‘type’ =\> ‘Diners Club US & Canada’, ‘key’ =\>  
[55], ‘length’ =\> [16]],  
Hash[‘type’ =\> ‘Discover’, ‘key’ =\>  
[6011, 65], ‘length’ =\> [16]],  
Hash[‘type’ =\> ‘JCB’, ‘key’ =\>  
[35], ‘length’ =\> [16]],  
Hash[‘type’ =\> ‘JCB’, ‘key’ =\>  
[1800, 2131], ‘length’ =\> [15]],  
Hash[‘type’ =\> ‘Maestro’, ‘key’ =\>  
[5020, 5038, 6759], ‘length’ =\> [16]],  
Hash[‘type’ =\> ‘MasterCard’, ‘key’ =\>  
(51…55).to\_a, ‘length’ =\> [16]],  
Hash[‘type’ =\> ‘Solo’, ‘key’ =\>  
[6334, 6767], ‘length’ =\> [16, 18, 19]],  
Hash[‘type’ =\> ‘Switch’, ‘key’ =\>  
[4903, 4905, 4911, 4936, 564182, 633110, 6333, 6759],

‘length’ =\> [16, 18, 19]],  
Hash[‘type’ =\> ‘Visa’, ‘key’ =\>  
[4], ‘length’ =\> [13, 16]],  
Hash[‘type’ =\> ‘Visa Electron’, ‘key’ =\> [417500,  
4917, 4913], ‘length’ =\> [16]]  
]

#number should be an array of numbers as strings e.g. [“1”, “2”, “3”]  
def initialize(array)  
@number = array.collect{|num| num.to\_i}  
end

def type  
names = Array.new  
TYPES.each do |company|  
company[‘key’].each do |key|  
if company[‘length’].include?(@number.length) and  
@number.join.begins\_with?(key.to\_s)  
names \<\< company[‘type’]  
end  
end  
end  
names.empty? ? [“Unknown”] : names  
end

def valid?  
temp = @number.reverse.collect\_with\_index{|num, index| index%2 == 0  
? num\*2 : num}  
sum = temp.collect{|num|num \> 9 ? [1, num%10] :  
num}.flatten.inject{|s, n| s+n}  
sum%10 == 0  
end

def process  
puts “The card type is #{self.type.join(’ or ')}”  
puts “The card number is #{self.valid? ? ‘valid’ : ‘invalid’}”  
end  
end

if $0 == **FILE**  
abort “You must enter a number!” if ARGV.empty?  
CCNumber.new(ARGV.join.strip.split(//)).process  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 30, 2007, 7:44am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/51 "2007-04-30T07:44:10Z")

</div>

> This week’s Ruby Q. is to write a program that accepts a credit card  
> number as a command-line argument. The program should print the card’s  
> type (or Unknown) as well a Valid/Invalid indication of whether or not the  
> card passes the Luhn algorithm.

This is just a class, with a bit of starter code at the bottom to get  
going.  
It will take any number of CC numbers on the command line, but they must  
be  
quoted. The luhn method seems tacky, but seems to work.

#################################  
class CCNumberError \< StandardError  
end

class CardValidate  
attr\_reader :cc\_number, :cc\_type, :luhn\_valid  
def initialize(cc\_number)  
@cc\_number = cc\_number  
normalise\_cc\_number  
card\_type  
luhn  
end

private  
def normalise\_cc\_number  
@cc\_number = @cc\_number.gsub(" ", “”)  
if @cc\_number =~ /\D/  
raise CCNumberError, “Credit Card numbers may not contain non  
digit  
characters except spaces”, caller  
end  
@cc\_length = @cc\_number.length  
end

def card\_type  
if @cc\_length == 15  
if @cc\_number[0…1].to\_i == 34 or @cc\_number[0…1].to\_i == 37  
@cc\_type = “American Express”  
end  
elsif @cc\_length == 16 and @cc\_number[0…3] == 6011  
@cc\_type = “Discover”  
elsif @cc\_length == 16 and (51…55) === @cc\_number[0…1].to\_i  
@cc\_type = “MasterCard”  
elsif @cc\_length == 16 or @cc\_length == 13  
if @cc\_number.index(“4”) == 0  
@cc\_type = “Visa”  
end  
else  
@cc\_type = “Unknown”  
end  
end

def luhn  
ccn = @cc\_number.reverse.scan(/\d/)  
ccn\_luhn\_sum = 0  
i = 0  
ccn.length.times do  
if i % 2 == 0  
ccn\_luhn\_sum += ccn[i].to\_i  
else  
if ccn[i].to\_i \* 2 \>= 10  
n = (ccn[i].to\_i \* 2).to\_s  
ccn\_luhn\_sum += n[0].chr.to\_i  
ccn\_luhn\_sum += n[1].chr.to\_i  
else  
ccn\_luhn\_sum += ccn[i].to\_i \* 2  
end  
end  
i += 1  
end  
ccn\_luhn\_sum % 10 == 0 ? @luhn\_valid = true : @luhn\_valid = false  
end  
end

ARGV.each do |n|  
card = CardValidate.new(n)  
puts “Card number: #{card.cc\_number}”  
puts “Card type: #{card.cc\_type}”  
puts “Luhn valid: #{card.luhn\_valid}”  
puts  
end  
#######################

-d

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [April 30, 2007, 5:08pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/52 "2007-04-30T17:08:43Z")

</div>

My solution is below.

# file: credit\_card.rb

# author: Drew O.

class CreditCard  
def initialize num  
@number = num  
end

# check specified conditions to determine the type of card

def type  
length = @number.size  
if length == 15 && @number =~ /^(34|37)/  
“AMEX”  
elsif length == 16 && @number =~ /^6011/  
“Discover”  
elsif length == 16 && @number =~ /^5[1-5]/  
“MasterCard”  
elsif (length == 13 || length == 16) && @number =~ /^4/  
“Visa”  
else  
“Unknown”  
end  
end

# determine if card is valid based on Luhn algorithm

def valid?  
digits = ‘’  
# double every other number starting with the next to last  
# and working backwards  
@number.split(’’).reverse.each\_with\_index do |d,i|  
digits += d if i%2 == 0  
digits += (d.to\_i\*2).to\_s if i%2 == 1  
end

```
# sum the resulting digits, mod with ten, check against 0
digits.split('').inject(0){|sum,d| sum+d.to_i}%10 == 0

```

end  
end

if **FILE** == $0  
card = CreditCard.new(ARGV.join.chomp)  
puts “Card Type: #{card.type}”  
if card.valid?  
puts “Valid Card”  
else  
puts “Invalid Card”  
end  
end

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 2, 2007, 3:18am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/53 "2007-05-02T03:18:20Z")

</div>

My second ever Ruby Q…  
TIA for any suggestions for making it more Ruby-like.  
/Bob

#!/usr/bin/env ruby -w

class CreditCard  
attr\_reader :number, :type, :validity  
def initialize(cardnumber)  
@number = cardnumber.gsub(/\s/,’’)  
@type = case @number  
when /^3[47]\d{13}$/ then “AMEX”  
when /^6011\d{12}$/ then “Discover”  
when /^5[12345]\d{14}$/ then “Mastercard”  
when /^4\d{12}$/ then “VISA”  
when /^4\d{15}$/ then “VISA”  
else “Unknown”  
end  
sum = 0  
digits = @number.to\_s.split(’’).reverse.map {|i| i.to\_i}  
digits.each\_index {|i| i%2==0 ? sum+=add\_digits(digits[i]) : sum  
+=add\_digits(digits[i]\*2)}  
@validity = sum%10 == 0 ? “Valid” : “Invalid”  
end  
def add\_digits(n)  
return n.to\_s.split(’’).inject(0) {|sum, i| sum += i.to\_i}  
end  
end #CreditCard

c = CreditCard.new(ARGV.join)  
puts “#{c.number}: #{c.type}\t#{c.validity}”

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 2, 2007, 5:13pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/54 "2007-05-02T17:13:38Z")

</div>

Ruby Q. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) writes:

> This week’s Ruby Q. is to write a program that accepts a credit card number as  
> a command-line argument. The program should print the card’s type (or Unknown)  
> as well a Valid/Invalid indication of whether or not the card passes the Luhn  
> algorithm.

#!ruby

def cardtype(n)  
case n.delete(“^0-9”)  
when /\A3[37]\d{13}\z/: “AMEX”  
when /\A6011\d{12}\z/: “Discover”  
when /\A5[1-4]\d{14}\z/: “Master Card”  
when /\A4\d{12}\d{3}?\z/: “Visa”  
else “Unknown”  
end  
end

def luhn?(n)  
f = 2  
(n.delete(“^0-9”).reverse.split(//).map{|d|d.to\_i}.  
inject(0) { |a,e| f=3-f; a + (e_f \> 9 ? e_f-9 : e\*f) } % 10).zero?  
end

puts cardtype(ARGV.join)  
puts luhn?(ARGV.join) ? “valid” : “invalid”

**END**

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 2, 2007, 7:14pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/55 "2007-05-02T19:14:53Z")

</div>

Am 02.05.2007 um 17:13 schrieb Christian N.:

> #!ruby
> 
> **END**
> 
> –  
> Christian N. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) http://  
> [chneukirchen.org](http://chneukirchen.org)

Wow, very nice solution! Impressive how short it is…

Here is my solution:

#!/usr/bin/env ruby -W

# Assign a regular expression that checks first characters and length

PROVIDERINFO = {  
“AMEX” =\> /^(34|37)\d{13}$/,  
“Discover” =\> /^6011\d{12}$/,  
“MasterCard” =\> /^5[1-5]\d{14}$/,  
“Visa” =\> /^4(\d{12}|\d{15})$/,  
}

class CreditCard  
attr\_reader :provider, :number

def initialize(number)  
@number = []  
# split credit card number and store in array  
number.scan(/\d/){|c| @number.push c.to\_i}

```
 # Check Provider Infos
 @provider = "Unknown"
 PROVIDERINFO.each_pair {|k, v| @provider = k if

```

@number.join.match(v) }  
end

def luhn\_passed?  
sum = 0  
@number.reverse.each\_with\_index do |num, i|  
# double the nummer if necessary and subtract 9 if the result  
# consists of 2 numbers (here same as summing up both numbers)  
num = num \* 2 - ((num \> 4) ? 9 : 0) if i % 2 == 1  
sum += num  
end  
sum % 10 == 0  
end

def to\_s  
“Creditcard number #{@number}\n” +  
" Provider: #{self.provider}\n" +  
" Luhn Algorithm #{'not ’ unless self.luhn\_passed?}passed"  
end  
end

puts CreditCard.new(ARGV.join)

Regards,

Dennis Frommknecht

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 2, 2007, 10:19pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/56 "2007-05-02T22:19:15Z")

</div>

Just one minor nit:

Christian N. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) writes:

> def luhn?(n)  
> f = 2  
> (n.delete(“^0-9”).reverse.split(//).map{|d|d.to\_i}.  
> inject(0) { |a,e| f=3-f; a + (e_f \> 9 ? e_f-9 : e\*f) } % 10).zero?  
> end

You do know that (e_f \> 9 ? e_f-9 : e_f) is equivalent to e_f%9,  
right? So that makes this method:

def luhn?(n)  
f = 2  
(n.delete(“^0-9”).reverse.split(//).map{|d|d.to\_i}.  
inject(0) { |a,e| f=3-f; a + e\*f%9 } %10).zero?  
end

(I still like my short version better, but too each his own)

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 2, 2007, 10:27pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/57 "2007-05-02T22:27:18Z")

</div>

On May 2, 2007, at 3:18 PM, Daniel M. wrote:

> You do know that (e_f \> 9 ? e_f-9 : e_f) is equivalent to e_f%9,  
> right?

> > (0…9).map { |n| n \* 2 % 9 }  
> > =\> [0, 2, 4, 6, 8, 1, 3, 5, 7, 0]
> 
> > (0…9).map { |n| n \* 2 \> 9 ? n \* 2 - 9 : n \* 2 }  
> > =\> [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]

James Edward G. II

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 3, 2007, 3:50am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/58 "2007-05-03T03:50:41Z")

</div>

Thank you James for another fun quiz.

My solution is a more verbose version of the metaprogramming-based  
routing used in why’s excellent camping. But instead of regexen  
matching urls to controller classes, here they are matching card  
numbers to the card classes.

What I like about this approach is that you can add more card patterns  
just by adding more classes with the appropriate regexen, and you can  
override validation or other functionality as needed.

Regards,

Paul.

# cardvalidator.rb

# 

require ‘metaid’

module Card  
@cards=[]

def Card.Base \*u  
c = @cards  
Class.new {  
meta\_def(:patterns){u}  
meta\_def(:validity){|x|Card.validity(x)}  
meta\_def(:inherited){|x|c\<\<x}  
}  
end  
def Card.validate cardnum  
@cards.map { |k|  
k.patterns.map { |x|  
if cardnum =~ /^#{x}/?$/  
return [k.name.upcase, k.validity(cardnum)].join( " “)  
end  
}  
}  
raise “Unexpected Card Number pattern: ‘#{cardnum}’”  
end  
def Card.sum\_of\_digits s  
s.split(”").inject(0){|sum,x|sum + Integer(x)}  
end  
def Card.luhnSum s  
temp = “”  
r = 0…s.size  
a = s.split("").reverse  
r.each do |i|  
if i%2==1  
x = (Integer(a[i])\*2).to\_s  
else  
x = a[i]  
end  
temp \<\< x.to\_s  
end  
sum\_of\_digits temp  
end  
def Card.validity cardnum  
if (Card.luhnSum(cardnum) % 10)==0  
return “Valid”  
else  
return “Invalid”  
end  
end  
end

# patterns will be tested for match in order defined

class Visa \< Card.Base /4[0-9]{12,15}/  
end  
class Amex \< Card.Base /3[4,7][0-9]{13}/  
end  
class Mastercard \< Card.Base /5[1-5][0-9]{13}/  
end  
class Discover \< Card.Base /6011[0-9]{12}/  
end

# catch-all

class Unknown \< Card.Base /.+/  
end

p Card.validate(ARGV[0])

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 4, 2007, 3:32pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/59 "2007-05-04T15:32:36Z")

</div>

Daniel M. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) writes:

> You do know that (e_f \> 9 ? e_f-9 : e_f) is equivalent to e_f%9,  
> right? So that makes this method:

I first thought that too, and it cost me 10min to find that 9%9 == 0.

[Previous page](https://www.ruby-forum.com/t/checking-credit-cards-122/93097.md?page=2)
