[QUIZ] Checking Credit Cards (#122)

J

Jeremy Hinegardner

The first check people often do is to validate that the card matches a known
pattern from one of the accepted card providers. Some of these patterns are:

+============+=============+===============+
| Card Type | Begins With | Number Length |
+============+=============+===============+
| AMEX | 34 or 37 | 15 |
+------------+-------------+---------------+
| Discover | 6011 | 16 |
+------------+-------------+---------------+
| 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).

http://en.wikipedia.org/wiki/Credit_card_number

enjoy,

-jeremy
 
A

Ari Brown

On Apr 27, 2007, at 7:59 AM, Ruby Quiz wrote:
1. Starting with the next to last digit and continuing with every
other
digit going back to the beginning of the card, double the digit
2. Sum all doubled and untouched digits in the number
3. If that total is a multiple of 10, the number is valid

For example, given the card number 4408 0412 3456 7893:

Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3
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.
 
P

Philip Gatt

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
 
P

Phrogz

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

Harry

This week's Ruby Quiz 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 Quiz has been translated into Japanese.
Maybe there will be more people participating.

http://d.hatena.ne.jp/nappa_zzz/20070429

Harry
 
J

James Edward Gray II

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

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 Gray II
 
R

Raj Sahae

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 Sahae

# ccc.rb
# Checking Credit Cards
# By Raj Sahae

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 = yield(x, i)
end
end
end

class CCNumber
#This data was taken from http://en.wikipedia.org/wiki/Credit_card_number
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
 
A

anansi

Raj said:
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 Sahae

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
 
A

anansi

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([email protected])}"
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
 
M

Morton Goldberg

Here is my solution to Quiz 122:

<code>
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
</code>

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
 
H

Harry

This week's Ruby Quiz 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 if u %2 == 0
arr2 << (arr.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
 
J

Jesse Merriman

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 Quiz 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 :mad:prefixes, prefixes
add_set_ivar :mad: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 *= 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
 
J

Joseph Seaton

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} <card number>"
else
puts ctype(card)
puts luhncheck(card)
end
###

Joe
 
S

Sebastian Hungerecker

Ruby said:
This week's Ruby Quiz 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]
[email protected](//).reverse.map {|x| x.to_i}
arr.each_with_index{|v,i| arr=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
 
C

Christoffer Lernö

--Apple-Mail-1--172385223
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

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)

####

--Apple-Mail-1--172385223
Content-Transfer-Encoding: 7bit
Content-Type: text/x-ruby-script;
x-unix-mode=0755;
x-mac-creator=54784D74;
name=cardcheck.rb
Content-Disposition: attachment;
filename=cardcheck.rb

#!/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)


--Apple-Mail-1--172385223
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed



--Apple-Mail-1--172385223--
 
R

Robert Dober

Yet another Great Ruby Quiz ;)
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
 
G

Gordon Thiesfeld

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
 
A

Ari Brown

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!

<snip>

-------------------------------------------------------|
~ Ari
crap my sig won't fit
 
A

Ari Brown

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

Ryan Leavengood

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 <credit card number> 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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,128
Latest member
ElwoodPhil
Top