This is not meant to be an exhaustive list of all possible numbers, nor the only or best method to verify that they pass the “checksum” test, but here’s what I came up with.
I wrote this mostly to link a Ruby version of the code to Wikipedia’s article on Luhn checksum validation, since nearly every other language in use was listed, but Ruby was sadly missing.
1 #!/usr/bin/env ruby
2
3 #
4 # Copyright (c) 2008 Michael Graff. All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or
7 # without modification, are permitted provided that the following
8 # conditions are met:
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials provided
14 # with the distribution.
15 # 3. The name of Michael Graff may not be used to endorse or promote
16 # products derived from this software without specific prior
17 # written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY Michael Graff ``AS IS'' AND ANY
20 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Micahel Graff
23 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25 # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
30 # OF SUCH DAMAGE.
31 #
32
33 class Luhn
34 public
35 def self.check_luhn(s)
36 s.gsub!(/[^0-9]/, "")
37 ss = s.reverse.split(//)
38
39 alternate = false
40 total = 0
41 ss.each do |c|
42 if alternate
43 total += double_it(c.to_i)
44 else
45 total += c.to_i
46 end
47 alternate = !alternate
48 end
49 (total % 10) == 0
50 end
51
52 private
53 def self.double_it(i)
54 i = i * 2
55 if i > 9
56 i = i % 10 + 1
57 end
58 i
59 end
60 end
61
62 if $0 == __FILE__
63 def test_valid(s)
64 result = Luhn::check_luhn(s)
65 if result
66 puts "VALID: #{s}"
67 else
68 puts "INVALID: #{s} (should be valid)"
69 end
70 end
71
72 test_valid('5105 1051 0510 5100') # Mastercard
73 test_valid('5555 5555 5555 4444') # Mastercard
74
75 test_valid('4222 2222 2222 2') # Visa
76 test_valid('4111 1111 1111 1111') # Visa
77 test_valid('4012 8888 8888 1881') # Visa
78
79 test_valid('3782 8224 6310 005') # American Express
80 test_valid('3714 4963 5398 431') # American Express
81 test_valid('3787 3449 3671 000') # American Express Corporate
82 test_valid('3782 8224 6310 005') # Amex
83 test_valid('3400 0000 0000 009') # Amex
84 test_valid('3700 0000 0000 002') # Amex
85
86 test_valid('38520000023237') # Diners Club (14 digits)
87 test_valid('30569309025904') # Diners Club (14 digits)
88
89 test_valid('6011111111111117') # Discover (16 digits)
90 test_valid('6011 0000 0000 0004') # Discover
91 test_valid('6011 0000 0000 0012') # Discover
92 test_valid('6011000990139424') # Discover (16 digits)
93 test_valid('6011601160116611') # Discover (16 digits)
94
95 test_valid('3530111333300000') # JCB (16 digits)
96 test_valid('3566002020360505') # JCB (16 digits)
97
98 test_valid('5431111111111111') # Mastercard (16 digits)
99 end