# 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:** 20\
**Page:** 2

<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:05am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/21 "2007-04-29T05:05:12Z")

</div>

On Apr 27, 2007, at 7:59 AM, Ruby Q. wrote:

> Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70  
> Step 3: 70 % 10 == 0
> 
> Thus that card is valid.

Uh, this is probably just affecting me but…

In his example, after he doubles the second to last digit (call it  
d), he uses mod10 on it (9\*2 = 18 %10 = 8). That is the way to get  
his numbers, but a) where does he say that and b) where do the 10 and  
14 come from?

Help me, hyperactive ruby posters!  
~ Ari  
English is like a pseudo-random number generator - there are a  
bajillion rules to it, but nobody cares.

---

<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 28, 2007, 6:49pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/22 "2007-04-28T18:49:36Z")

</div>

On Fri, Apr 27, 2007 at 08:59:49PM +0900, Ruby Q. wrote:

> | MasterCard | 51-55 | 16 |  
> ±-----------±------------±--------------+  
> | Visa | 4 | 13 or 16 |  
> ±-----------±------------±--------------+

Wikipedia has a great chart showing all the prefixes of most known  
credit cards, along with lengths and overlap between cards (currently  
none).

> **[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...

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, 5:31am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/23 "2007-04-29T05:31:13Z")

</div>

You move back to the beginning after doubling the 2nd to last. You  
double every other one on the way back to the beginning. The 10 is  
5_2 and the 14 is 7_2

---

<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:51am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/24 "2007-04-29T05:51:14Z")

</div>

On Apr 28, 9:04 pm, Ari B. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> > Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70  
> > Step 3: 70 % 10 == 0
> 
> > Thus that card is valid.
> 
> Uh, this is probably just affecting me but…
> 
> In his example, after he doubles the second to last digit (call it  
> d), he uses mod10 on it (9\*2 = 18 %10 = 8). That is the way to get  
> his numbers, but a) where does he say that and b) where do the 10 and  
> 14 come from?

The confusing part (that I didn’t catch when I read it) is that step 2  
is to sum all the _digits_, not the numbers.

So  
step 1) 9 \* 2 = 18  
step 2) 1 + 8

He’s not modding the result of the multiplication by 10, but rather  
adding up the resulting component digits. The same occurs with the 10  
and 14 (which Philip pointed out are the result of 5_2 and 7_2,  
respectively).

---

<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, 6:23am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/25 "2007-04-29T06:23:16Z")

</div>

On Apr 28, 2007, at 10:50 PM, Phrogz wrote:

> > his numbers, but a) where does he say that and b) where do the 10 and  
> > adding up the resulting component digits. The same occurs with the 10  
> > and 14 (which Philip pointed out are the result of 5_2 and 7_2,  
> > respectively).

Yes, most descriptions tell you to mod 10 the bigger numbers to get  
the back to a single digit, but that adds a step that doesn’t have  
any affect. I left it out.

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:** [April 29, 2007, 5:48am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/26 "2007-04-29T05:48:14Z")

</div>

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

> 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 Ruby Q. has been translated into Japanese.  
Maybe there will be more people participating.

[http://d.hatena.ne.jp/nappa\_zzz/20070429](http://d.hatena.ne.jp/nappa_zzz/20070429)

Harry

---

<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:14am UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/27 "2007-04-29T07:14:19Z")

</div>

On Apr 28, 2007, at 8:04 PM, Ari B. wrote:

> In his example, after he doubles the second to last digit (call it  
> d), he uses mod10 on it (9\*2 = 18 %10 = 8). That is the way to get  
> his numbers, but a) where does he say that and b) where do the 10  
> and 14 come from?

This confused me at first, too. Let’s take just the last four  
digits: 7893. Counting from the end, double every second digit,  
leaving the others unchanged. That gives you: 14, 8, 18, 3 (where  
14=7_2 and 18=9_2; the 8 and the 3 are unchanged). Now add up every  
digit of the resulting sequence: 1+4 + 8 + 1+8 + 3. Note the “1  
+4” comes from the 14, and the “1+8” comes from the 18.

Does that help?

-Mark

---

<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, 12:44pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/28 "2007-04-29T12:44:42Z")

</div>

My second ever rubyquiz submission, so be nice.  
Sorry if the submission is a little early (by about an hour and a half),  
but I won’t be around a comp tomorrow.

Raj S.

# ccc.rb

# Checking Credit Cards

# By Raj S.

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  
TYPES.each do |company|  
company[‘key’].each do |key|  
if company[‘length’].include?(@number.length) and  
@number.join.begins\_with?(key.to\_s)  
return company[‘type’]  
end  
end  
end  
“Unknown”  
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}”  
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 29, 2007, 1:30pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/29 "2007-04-29T13:30:45Z")

</div>

Raj S. wrote:

> My second ever rubyquiz submission, so be nice.  
> Sorry if the submission is a little early (by about an hour and a half),  
> but I won’t be around a comp tomorrow.
> 
> Raj S.

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

–  
greets  
(  
)  
(  
/\ .-"""-. /  
//\/ , //\  
|/| ,;;;;;, |/|  
//\;-"""-;///\  
// / . / \  
(| ,-_| \ | / |_-, |)  
//` __\.-.-./__ `\  
// /.-(() ())-.\ \  
(\ |) ‘—’ (| /)  
`(| |)`  
jgs ) (/

one must still have chaos in oneself to be able to give birth to a  
dancing star

---

<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, 2:05pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/30 "2007-04-29T14:05:49Z")

</div>

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

> 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.

###### 

# Check Starting numbers and length

def card\_ok(str,info)  
check = “UNKNOWN”  
info.each do |t|  
pref = t[0]  
leng = t[1]  
name = t[2]  
pref.each do |x|  
if x == str.slice(0…x.length)  
leng.each do |y|  
if y.to\_i == str.length  
check = name.dup  
end  
end  
end  
end  
end  
return check  
end

# Check Luhn algorithm

def luhn(str)  
luhn\_hash = Hash.new(“INVALID”)  
if str.length != 0  
luhn\_hash[0] = “VALID”  
arr = str.split(//).reverse  
arr2 = []  
arr3 = []  
(0…arr.length).each do |u|  
arr2 \<\< arr[u] if u %2 == 0  
arr2 \<\< (arr[u].to\_i \* 2).to\_s if u %2 != 0  
end

```
arr2.each do |r|
arr3 << r.split(//).inject(0) {|sum,i| sum + i.to_i}
end

```

val = arr3.inject(0) {|sum,i| sum + i} % 10  
end  
return luhn\_hash[val]  
end

# Card information

test = []  
test \<\< [[“31”,“34”],[“15”],“AMEX”]  
test \<\< [[“6011”],[“16”],“DISCOVER”]  
test \<\< [(“51”…“55”).to\_a,[“16”],“MASTERCARD”]  
test \<\< [[“4”],[“13”,“16”],“VISA”]  
#test \<\< [(“3528”…“3589”).to\_a,[“16”],“JCB”]  
#test \<\< [[(“3000”…“3029”).to\_a + (“3040”…“3059”).to\_a +  
#(“3815”…“3889”).to\_a + [“36”,“389”]].flatten!,[“14”],“DINERS”]

# Main

str = ARGV.join  
arr = []  
arr \<\< card\_ok(str,test)  
arr \<\< luhn(str)  
puts arr

###### 

Harry

---

<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, 2:04pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/31 "2007-04-29T14:04:49Z")

</div>

Here is my solution to Quiz 122:

`
class CardChecker
    def initialize(card_num)
       @card_num = card_num
       @issuer = case
          when visa? then 'VISA'
          when mastercard? then 'MasterCard'
          when amex? then 'AMEX'
          when discover? then 'Discover'
          else 'UNKNOWN'
       end
       @valid = valid?
    end
    def visa?
       (@card_num.size == 13 || @card_num.size == 16) && @card_num
=~ /^4/
    end
    def mastercard?
       @card_num.size == 16 && @card_num =~ /^5[1-5]/
    end
    def amex?
       @card_num.size == 15 && @card_num =~ /^3[47]/
    end
    def discover?
       @card_num.size == 16 && @card_num =~ /^6011/
    end
    def valid?
       digits = @card_num.reverse.split('')
       sum = 0
       digits.each_with_index do |e, i|
          d = e.to_i
          if i & 1 == 0
             sum += d
          else
             q, r = (d + d).divmod(10)
             sum += q + r
          end
       end
       sum % 10 == 0
    end
    def to_s
       @issuer + (@valid ? " " : " IN") + "VALID"
    end
end
if $0 == FILE
puts CardChecker.new(ARGV.join)
end
`

This was a very easy (I like easy) but fun quiz. I believe this is  
the first time I actually completed a quiz solution, including  
testing, in less than an hour.

Regards, Morton

---

<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, 2:08pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/32 "2007-04-29T14:08:50Z")

</div>

48 hours are past so here is my first solution. I’m just coding ruby for  
a few days so any feedback is welcome 🙂

My script recognizes 12 different creditcard companys and of course says  
if a card could belong to two different companys at the same time or if  
it is unknown.

I think the companycheck part could be done better by calling check.call  
from a loop but I couldn’t figure out how to pass such an hash:

hash = Hash.new { |hash, key| hash[key] = [] }  
raw\_data = [[1,“American Express”],[1,/^34|^37/], [1, “15”],  
[2,“Diners CLub Blanche”],[2,/^30[0-5]/], [2, “14”],  
[3,“Solo”],[3,/^6334|^6767/],[3,“16”],[3,“18”],[3,“19”]]  
raw\_data.each { |x,y| hash[x] \<\< y }

to

check = lambda{|reg,c,\*d| if number =~ reg: @company += " or " + c if  
d.include?(number.length) end }

in a loop. Any idea how to do this?

here the solution:

#!/usr/bin/ruby -w

# validate.rb

######################################################

# validator for creditcard numbers

# by anansi

# 29/04/07 on comp.lang.rub

# 

# [QUIZ] Checking Credit Cards (#122)

######################################################

# recognizes:

# American Express,

# Diners CLub Blanche,

# Diners CLub International,

# Diners Club US & Canada,

# Discover,

# JCB,

# Maestro (debit card),

# Mastercard,

# Solo,

# Switch,

# Visa,

# Visa Electron

######################################################

class CreditCard

def initialize(number)  
number.scan(/\D/) { |x| # scans number for every not-digit  
symbol  
puts x + " is no valid credit card  
symbol.\nJust digits allowed!!"  
exit  
}  
end

def companycheck(number)  
@company= “”  
# block check compares the length and sets the company value  
check = lambda{|reg,c,\*d| if number =~ reg: @company += " or " + c  
if d.include?(number.length) end }  
# adding a new bank is quite easy, just put a new check.call in  
with:  
# check.call(regular expressions for the starting  
bytes,“Company-name”,length1,length2,…)  
# I’m sure this can be done somehow better invoking check.call by a  
loop  
# but I couldn’t figure out how to pass a array with dynamic  
variable count into check.call  
check.call( /^34|^37/ , “American Express” , 15 )  
check.call( /^30[0-5]/ ,“Diners CLub Blanche”,14)  
check.call( /^36/ , “Diners CLub International”,14)  
check.call( /^55/ , “Diners Club US & Canada”,16)  
check.call( /^6011|^65/ , “Discover”,16)  
check.call( /^35/ , “JCB” , 16)  
check.call( /^1800|^2131/ , “JCB” , 15)  
check.call( /^5020|^5038|^6759/ , “Maestro (debit card)” , 16)  
check.call( /^5[0-5]/ , “Mastercard” , 16)  
check.call( /^6334|^6767/ , “Solo” , 16 , 18 , 19)  
check.call( /^4903|^4905|^4911|^4936|^564182|^633110|^6333|^6759/ ,  
“Switch” , 16 , 18 , 19)  
check.call( /^4/ , “Visa” , 13 , 16)  
check.call( /^417500|^4917|^4913/ , “Visa Electron” , 16)  
if @company == “”  
puts “Company : Unknown”  
else  
puts “Company : #{@company.slice(removed\_email\_address@domain.invalid)}”  
end  
end

def crossfoot(digit)  
digit = “%2d” % digit # converts  
integer to string  
if digit[0] == 32  
digit = (digit[1].to\_i) -48  
else # if the doubled  
digit has more than 2 digits  
digit= (digit[0].to\_i) + (digit[1].to\_i) -96 # adds the single  
digits and converts back to integer  
end  
end

def validation(number)  
math = lambda { |dig| number[@count-dig]-48 } # block math converts  
str to int of the current digit  
@duplex = false  
@count = number.length  
@result = 0  
for i in (1…@count)  
if @duplex == false # for every first digit from the  
back  
@result += math.call i # add to result  
@duplex = true  
else # for every second digit  
from the back  
@result += crossfoot((math.call i)\*2) # mutl. digit with 2, do the  
crossfoot and add to result  
@duplex = false  
end  
end  
@result.modulo(10)  
end

end

#### begin

if ARGV.length == 0 # checks if argument is  
passed  
puts “no input\nusage, e.g.: ruby validate.rb 4408 0412 3456 7893”  
exit  
end

number = ARGV.join(’’).gsub(" “,”") # reads args and kills all  
spaces and newlinefeed

my\_creditcard = CreditCard.new(number) # checks if just digits are  
inputed otherwise: abort.

my\_creditcard.companycheck(number) # checks for known or unknown  
company

if my\_creditcard.validation(number) == 0 # checks validation with  
luhn-algo  
puts “Validation: successful”  
else  
puts “Validation: failure”  
end

### eof

–  
greets  
(  
)  
(  
/\ .-"""-. /  
//\/ , //\  
|/| ,;;;;;, |/|  
//\;-"""-;///\  
// / . / \  
(| ,-_| \ | / |_-, |)  
//` __\.-.-./__ `\  
// /.-(() ())-.\ \  
(\ |) ‘—’ (| /)  
`(| |)`  
jgs ) (/

one must still have chaos in oneself to be able to give birth to a  
dancing star

---

<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, 2:20pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/33 "2007-04-29T14:20:51Z")

</div>

My solution (the second I’ve submitted, so please be nice :).  
Suggestions welcome.

### 

BCTYPES = {  
[[34,37],[15]] =\> “AMEX”,  
[[6011],[16]] =\> “Discoverer”,  
[(51…57).to\_a,16] =\> “MasterCard”,  
[[4],[13,16]] =\> “Visa”}

def ctype(num)  
BCTYPES.each { |n,t| n[0].each { |s|  
return t if num.grep(/^#{s}/).any? && n[1].include?(num.length)  
} }  
“Unknown”  
end

def luhncheck(num)  
e = false  
num.split(//).reverse.collect { |a| e=!e  
a.to\_i\*(e ? 1:2)  
}.join.split(//).inject(0) {|a,b| a+b.to\_i} % 10 == 0 ? “Valid” :  
“Invalid”  
end

card = ARGV.join.gsub(/ /, ‘’)  
if card == “”  
puts "Usage: #{$0} "  
else  
puts ctype(card)  
puts luhncheck(card)  
end

### 

Joe

---

<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, 2:16pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/34 "2007-04-29T14:16:50Z")

</div>

## For my solution, I created a Card class that holds a Set of Integers/Ranges for acceptable prefixes and lengths. I added a prefix\_of? method to both Integer and Range for checking whether they are prefixes of a String. Card#initialize is pretty flexible in arguments it takes thanks to an add\_set\_ivar method that turns numbers/sets/arrays/ranges into the kind of Sets that I want. From there, the Card#valid? method is straightforward

just call any? on the prefixes and lengths and make sure both are true.

The Card.luhn\_valid? method checks if a card number passes the Luhn  
algorithm. I messed around with a few different ways of doing it, and  
settled on a rather dense 4-liner.

#!/usr/bin/env ruby

# check\_credit\_card.rb

# Ruby Q. 122: Checking Credit Cards

require ‘set’

class Integer

# Determine if this number is a prefix of the given string for the

given base.  
def prefix\_of? str, base = 10  
not /^#{to\_s(base)}/.match(str).nil?  
end  
end

class Range

# Determine if any numbers within this range is a prefix of the given

string

# for the given base.

def prefix\_of? str, base = 10  
# We could use the first case every time, but the second is much  
quicker  
# for large ranges.  
if str[0].chr == ‘0’  
any? { |num| num.prefix\_of? str, base }  
else  
num = str.slice(0…(max.to\_s(base).length-1)).to\_i  
num \>= min and num \<= max  
end  
end  
end

class Card  
attr\_accessor :name

# Turn arg into a Set instance variable based on its class.

# This is so initialize can easily accept a few different argument

types.  
def add\_set\_ivar ivar, arg  
case arg  
when Set; instance\_variable\_set ivar, arg  
when Array; instance\_variable\_set ivar, Set.new(arg)  
else; instance\_variable\_set ivar, Set[arg]  
end  
end

# prefixes can be:

# - a single number

# - an Array of numbers

# - a Range of numbers

# - an Array of numbers and Ranges

# - a Set of numbers

# - a Set of numbers and Ranges

# 

# lengths can be:

# - a single number

# - an Array of numbers

# - a Set of numbers

def initialize name, prefixes, lengths  
@name = name  
add\_set\_ivar :@prefixes, prefixes  
add\_set\_ivar :@lengths, lengths  
end

# Determine if a number is valid for this card.

def valid? num  
num = num.to\_s  
@prefixes.any? { |pre| pre.prefix\_of? num } and  
@lengths.any? { |len| len == num.length }  
end

# Determine if the given number passes the Luhn algorithm.

# This is pretty damn dense… perhaps I should spread it out more…

def Card.luhn\_valid? num  
digits = num.to\_s.split(//).map! { |d| d.to\_i } # separate digits  
(digits.size-2).step(0,-2) { |i| digits[i] \*= 2 } # double every  
other  
digits.map! { |d| d \< 10 ? d : [1,d-10] }.flatten! # split up those

> 10  
> (digits.inject { |sum, d| sum + d } % 10).zero? # sum divisible  
> by 10?  
> end  
> end

if $0 == **FILE**  
CardPool = Set[  
Card.new(‘AMEX’, [34,37], 15),  
Card.new(‘Discover’, 6011, 16),  
Card.new(‘MasterCard’, (51…55), 16),  
Card.new(‘Visa’, 4, [13,16]),  
Card.new(‘JCB’, (3528…3589), 16),  
Card.new(‘Diners’,  
[(3000…3029),(3040…3059),36,(3815…3889),389], 14)  
]

card\_num = $stdin.gets.chomp.gsub! /\s/, ‘’  
cards = CardPool.select { |c| c.valid? card\_num }

if cards.size.zero?  
puts “Unknown card.”  
else  
puts “Number matched #{cards.size} cards:”  
cards.each { |c| puts " #{c.name}" }  
end

if Card.luhn\_valid?(card\_num); puts ‘Passed Luhn algorithm’  
else; puts ‘Failed Luhn algorithm’; 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, 2:37pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/35 "2007-04-29T14:37:55Z")

</div>

Ruby Q. wrote:

> 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.

I’m rather new to ruby and this is the first time I participated in this  
quiz.  
If found this one to be rather easy and fun to work with.  
Here’s my solution:

#!/usr/bin/env ruby

class CreditCard  
attr\_reader :number  
CardTypes = [  
{ :name =\> “AMEX”, :regex =\> /(34|37)\d{13}/, :luhn =\> true},  
{ :name =\> “Bankcard”, :regex =\> /5610\d{12}/, :luhn =\> true},  
{ :name =\> “Bankcard”, :regex =\> /56022[1-5]\d{10}/, :luhn =\> true},  
{ :name =\> “China Union Pay”, :regex =\> /622\d{13}/, :luhn =\>  
false},  
{ :name =\> “DC-CB”, :regex =\> /30[0-5]\d{11}/, :luhn =\> true},  
{ :name =\> “DC-eR”, :regex =\> /2(014|149)\d{11}/, :luhn =\> false},  
{ :name =\> “DC-Int”, :regex =\> /36\d{12}/, :luhn =\> true},  
{ :name =\> “DC-UC or MasterCard”, :regex =\> /55\d{14}/, :luhn =\>  
true},  
{ :name =\> “Discover”, :regex =\> /6011\d{12}/, :luhn =\> true},  
{ :name =\> “MasterCard”, :regex =\> /5[1-4]\d{14}/, :luhn =\> true},  
{ :name =\>“Maestro”, :regex =\> /(5020|5038|6759)\d{12}/, :luhn =\>  
true},  
{ :name =\> “Visa”, :regex =\> /4(\d{13}|\d{16})/, :luhn =\> true},  
{ :name =\> “Unknown”, :regex =\> //, :luhn =\> true} ]  
# If the credit card is of unknown type, we’ll just assume  
# that it can be verified using the Luhn algorithm.

def initialize(num)  
self.number=num  
end

def number=(num)  
raise ArgumentError, “Supplied argument is not a number” unless  
num.to\_s =~  
/^[-_\s\d]+$/  
@number=num.to\_s.gsub(/(\s|_|-)/,’’)  
@type=nil  
@validity=nil  
end

def card\_type  
@type||=CardTypes.detect {|i| i[:regex].match @number}  
end

def to\_s  
“Number: #{@number}, Type: #{card\_type[:name]}, Valid: #{valid?}”  
end

def valid?  
return @validity unless @validity.nil?  
return @validity=“unknown” unless card\_type[:luhn]  
arr=@number.split(//).reverse.map {|x| x.to\_i}  
arr.each\_with\_index{|v,i| arr[i]=v\*2 if i%2==1}  
sum=arr.join.split(//).map do |x| x.to\_i end.inject {|s,i| i+s}  
@validity = sum%10==0  
end  
end

if **FILE** ==$0  
card=CreditCard.new(if ARGV.empty?  
puts “Please enter your credit card  
number:”  
gets.chomp  
else  
ARGV.join  
end)  
puts card  
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, 2:47pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/36 "2007-04-29T14:47:55Z")

</div>

My solution:

#### 

#!/usr/bin/env ruby -w

class Numeric  
def to\_a  
[self]  
end  
end

class CardType

@@cards = []

def initialize(name, prefix, length)  
@name = name  
@prefix = prefix.to\_a  
@length = length.to\_a  
end

def match(string)  
@length.member?(string.length) && @prefix.find { |v| string =~ /  
^#{v.to\_s}/ }  
end

def to\_s  
@name  
end

def CardType.register(name, prefix, length)  
@@cards \<\< CardType.new(name, prefix, length)  
end

def CardType.luhn\_check(value)  
value.reverse.scan(/…{0,1}/).collect do |s|  
s[0…0] + (s[1…1].to\_i \* 2).to\_s  
end.join.scan(/./).inject(0) { |sum, s| sum + s.to\_i } % 10 == 0  
end

def CardType.find\_card(string)  
value = string.gsub(/[-]/,’’)  
return “Illegal Code” if value =~ /\W/  
“#{@@cards.find { |c| c.match(value) } || “Unknown”} [#  
{luhn\_check(value) ? “Valid” : “Invalid” }]”  
end

end

CardType.register “Maestro”, [5020, 5038, 6759], 16  
CardType.register “VISA”, 4, [13, 16]  
CardType.register “MasterCard”, 51…55, 16  
CardType.register “Discover”, [6011, 65], 16  
CardType.register “American Express”, [34, 37], 15  
CardType.register “Diners Club International”, 36, 14  
CardType.register “Diners Club Carte Blanche”, 300…305, 14  
CardType.register “JCB”, 3528…3589, 16

number = ARGV.join " "  
puts number + " =\> " + CardType.find\_card(number)

####

---

<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, 3:20pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/37 "2007-04-29T15:20:58Z")

</div>

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)} "  
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

---

<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, 3:22pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/38 "2007-04-29T15:22:58Z")

</div>

On Apr 28, 2007, at 11:30 PM, Philip G. wrote:

> You move back to the beginning after doubling the 2nd to last. You  
> double every other one on the way back to the beginning. The 10 is  
> 5_2 and the 14 is 7_2

Ohh, I see what he did now! Thanks for your help!

-------------------------------------------------------|  
~ Ari  
crap my sig won’t fit

---

<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, 3:24pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/39 "2007-04-29T15:24:59Z")

</div>

On Apr 29, 2007, at 1:14 AM, Mark Day wrote:

> This confused me at first, too. Let’s take just the last four  
> digits: 7893. Counting from the end, double every second digit,  
> leaving the others unchanged. That gives you: 14, 8, 18, 3 (where  
> 14=7_2 and 18=9_2; the 8 and the 3 are unchanged). Now add up every  
> digit of the resulting sequence: 1+4 + 8 + 1+8 + 3. Note the  
> “1+4” comes from the 14, and the “1+8” comes from the 18.

Tons, your and philip’s responses have definitely cleared this up.  
Now I can begin writing it.  
--------------------------------------------|  
If you’re not living on the edge,  
then you’re just wasting space.

---

<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, 2:47pm UTC](https://www.ruby-forum.com/t/checking-credit-cards-122/93097/40 "2007-04-29T14:47:55Z")

</div>

Yet another Great Ruby Q. 😉  
Well I thought this was a Quiz particularly suited to write _nice_  
code. I guess I somehow failed as it is too long, but I put some of  
the features I like most of Ruby, well I guess it is a solution which  
is consistent with my style 😉  
And as I feel that this is not said frequently enough, I’ll just say  
it: “Thank you James for all these Quizzes, and thanks to the Quiz  
creators too of course.”

Cheers  
Robert

#!/usr/bin/ruby

# vim: sts=2 sw=2 expandtab nu tw=0:

class String  
def to\_rgx  
Regexp.new self  
end

def ccc  
Checker.new{  
amex [34,37], 15  
discover 6011, 16  
master 50…55, 16  
visa 4, [13,16]  
jcb 3528…3589, 16  
}.check self  
end  
end

class Checker  
UsageException = Class.new Exception  
def initialize &blk  
@cards = {}  
instance\_eval &blk  
end

def check str  
s = str.gsub(/\s/,"")  
@cards.each do  
|card, check\_values|  
return [luhn( s ), card.to\_s.capitalize] if  
check\_values.first === s && check\_values.last.include?( s.length  
)  
end  
[nil, “Unknown”]  
end

def luhn s  
sum = 0  
s.split(//).reverse.each\_with\_index{  
| digit, idx |  
sum += (idx%2).succ \* digit.to\_i  
}  
(sum % 10).zero? ? " Valid" : “n Invalid”  
end

# This is one of the rare examples where

# the method\_missing parametes are not

# id, \*args, &blk, trust me I know what

# I am doing 😉

def method\_missing credit\_card\_name, regs, lens  
raise UsageException, “#{card\_name} defined twice” if  
@cards[credit\_card\_name]  
### Unifying Integer, Array and Range parameters  
lens = [lens] if Integer === lens  
lens = lens.to\_a  
### Genereating regular expressions  
regs = [regs] if Integer === regs  
regs = regs.map{ |r| “^#{r}” }.join("|").to\_rgx  
@cards[credit\_card\_name] = [regs, lens]  
end  
end

ARGV.each do  
| number |  
puts “Card with number #{number} is a%s %s card” %  
number.ccc

end # ARGV.each do

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

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