Implementing the Luhn algorithm in Ruby
Here's a quick one that works:
def credit_card_valid?(account_number)
digits = account_number.chars.map(&:to_i)
check = digits.pop
sum = digits.reverse.each_slice(2).flat_map do |x, y|
[(x * 2).divmod(10), y || 0]
end.flatten.inject(:+)
check.zero? ? sum % 10 == 0 : (10 - sum % 10) == check
end
credit_card_valid? "79927398713" #=> true
credit_card_valid? "79927398714" #=> false
I had problems running a lot of the above code, so I went ahead and developed a solution. Below is an my solution, along with test code and can be run with Ruby-cores AWESOME minitest library.
require "minitest"
require "minitest/autorun"
# Public: Validates number against Luhn 10 scheme
#
# Luhn Algo ripped from: http://en.wikipedia.org/wiki/Luhn_algorithm
# 1. From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if product of this doubling operation is greater than 9 (e.g., 7 * 2 = 14).
# 2. Sum the digits of the products (e.g., 10: 1 + 0 = 1, 14: 1 + 4 = 5) together with the undoubled digits from the original number.
# 3. If the total modulo 10 is equal to 0 (if the total ends in zero) then the number is valid according to the Luhn formula; else it is not valid.
#
# Returns true or false
def luhn_valid?(cc_number)
number = cc_number.
gsub(/\D/, ''). # remove non-digits
reverse # read from right to left
sum, i = 0, 0
number.each_char do |ch|
n = ch.to_i
# Step 1
n *= 2 if i.odd?
# Step 2
n = 1 + (n - 10) if n >= 10
sum += n
i += 1
end
# Step 3
(sum % 10).zero?
end
describe "Luhn Algorithm" do
# Cards ripped from paypal: http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm
CC_NUMBERS = {
"American Express" => "378282246310005",
"American Express 2" => "371449635398431",
"American Express Corporate" => "378734493671000",
"Australian BankCard" => "5610591081018250",
"Diners Club" => "30569309025904",
"Diners Club 2" => "38520000023237",
"Discover" => "6011111111111117",
"Discover 2" => "6011000990139424",
"JCB" => "3530111333300000",
"JCB 2" => "3566002020360505",
"MasterCard" => "5555555555554444",
"MasterCard 2" => "5105105105105100",
"Visa" => "4111111111111111",
"Visa 2" => "4012888888881881",
"Visa 3" => "4222222222222",
}
it "returns true for valid numbers" do
assert CC_NUMBERS.values.all? { |number| luhn_valid?(number) }
end
it "returns false for invalid numbers" do
CC_NUMBERS.values.each do |number|
me_turn_bad = (number.to_i + 1).to_s
refute luhn_valid?(me_turn_bad)
end
end
end