git clone git@gist.github.com:3713188.git ccvalidator
cd ccvalidator
cat input1.dat | ./validate_cards.rb
ruby test/credit_card_validator_spec.rb
git clone git@gist.github.com:3713188.git ccvalidator
cd ccvalidator
cat input1.dat | ./validate_cards.rb
ruby test/credit_card_validator_spec.rb
| # note -- assumption is that an Unknown type of card is invalid, regardless of the luhn validity | |
| # I also consider a card to be of Unknown type if the length doesn't match the valid length for a given prefix | |
| class CreditCardValidator | |
| attr_accessor :cardnum | |
| def initialize(cardnum) | |
| #remove spaces and dashes -- possibly should remove all non-digit characters? | |
| @cardnum = cardnum.gsub(/[\s-]/, '') | |
| end | |
| # check prefix digits and length | |
| def card_type | |
| return 'AMEX' if ['34','37'].include?(@cardnum[0..1]) && @cardnum.length == 15 | |
| return 'Discover' if @cardnum[0..3] == '6011' && @cardnum.length == 16 | |
| return 'MasterCard' if ['51','52','53','54','55'].include?(@cardnum[0..1]) && @cardnum.length == 16 | |
| return 'VISA' if @cardnum[0..0] == '4' && [13,16].include?(@cardnum.length) | |
| return 'Unknown' | |
| end | |
| def type_and_number | |
| "#{card_type}: #{cardnum}" | |
| end | |
| # check that the card number matches the luhn algorithm | |
| def valid_luhn | |
| # for odd length card numbers, we want to start by doubling the second digit instead of the first one | |
| offset = (@cardnum.length + 1) % 2 | |
| expanded_string = '' | |
| @cardnum.chars.each_with_index do |char, index| | |
| # double every other digit | |
| new_char = char.to_i * ((index + offset) % 2 + 1) | |
| expanded_string += new_char.to_s | |
| end | |
| sum_of_chars = expanded_string.chars.inject(0) do |result, char| | |
| result += char.to_i | |
| end | |
| return (sum_of_chars % 10) == 0 | |
| end | |
| def valid | |
| return card_type != 'Unknown' && valid_luhn | |
| end | |
| def printable_valid | |
| valid ? 'valid' : 'invalid' | |
| end | |
| def self.process_stream(istream) | |
| while !istream.eof && line = istream.readline | |
| validator = CreditCardValidator.new(line.strip) | |
| yield "#{validator.type_and_number.ljust(28)} (#{validator.printable_valid})" | |
| end | |
| end | |
| end |
| require 'minitest/autorun' | |
| require 'stringio' | |
| require_relative '../credit_card_validator.rb' | |
| describe "CreditCardValidator", "Complete credit card validation tests" do | |
| it "validates AMEX cards" do | |
| #test valid cases | |
| validator = CreditCardValidator.new("378282246310005") | |
| validator.card_type.must_equal 'AMEX' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("348282246310002") | |
| validator.card_type.must_equal 'AMEX' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("378282246310005") | |
| validator.card_type.must_equal 'AMEX' | |
| validator.valid.must_equal true | |
| #test invalid prefix | |
| validator = CreditCardValidator.new("358282246310017") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| #test invalid luhn | |
| validator = CreditCardValidator.new("348282246310016") | |
| validator.card_type.must_equal 'AMEX' | |
| validator.valid.must_equal false | |
| #test invalid length | |
| validator = CreditCardValidator.new("358282246310017") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| end | |
| it "validates Mastercards" do | |
| #test valid cases | |
| validator = CreditCardValidator.new("5103195004195007") | |
| validator.card_type.must_equal 'MasterCard' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("5203195004195006") | |
| validator.card_type.must_equal 'MasterCard' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("5303195004195005") | |
| validator.card_type.must_equal 'MasterCard' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("5403195004195004") | |
| validator.card_type.must_equal 'MasterCard' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("5503195004195003") | |
| validator.card_type.must_equal 'MasterCard' | |
| validator.valid.must_equal true | |
| #test invalid prefix | |
| validator = CreditCardValidator.new("5003195004195008") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| #test invalid luhn | |
| validator = CreditCardValidator.new("5503195004195004") | |
| validator.card_type.must_equal 'MasterCard' | |
| validator.valid.must_equal false | |
| #test invalid length | |
| validator = CreditCardValidator.new("55031950041953") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| end | |
| it "validates Discover cards" do | |
| #test valid cases | |
| validator = CreditCardValidator.new("6011111111111109") | |
| validator.card_type.must_equal 'Discover' | |
| validator.valid.must_equal true | |
| #test invalid prefix | |
| validator = CreditCardValidator.new("7011111111111107") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| #test invalid luhn | |
| validator = CreditCardValidator.new("6011111111111119") | |
| validator.card_type.must_equal 'Discover' | |
| validator.valid.must_equal false | |
| #test invalid length | |
| validator = CreditCardValidator.new("60111111111110") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| end | |
| it "validates Visa cards" do | |
| #test valid cases | |
| validator = CreditCardValidator.new("4211111111111110") | |
| validator.card_type.must_equal 'VISA' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("4211 1111 1111 1110") | |
| validator.card_type.must_equal 'VISA' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("4211-1111-1111-1110") | |
| validator.card_type.must_equal 'VISA' | |
| validator.valid.must_equal true | |
| validator = CreditCardValidator.new("4000000000006") | |
| validator.card_type.must_equal 'VISA' | |
| validator.valid.must_equal true | |
| #test invalid prefix | |
| validator = CreditCardValidator.new("3111111111111113") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| #test invalid luhn | |
| validator = CreditCardValidator.new("4111111111111110") | |
| validator.card_type.must_equal 'VISA' | |
| validator.valid.must_equal false | |
| #test invalid length | |
| validator = CreditCardValidator.new("400000000002") | |
| validator.card_type.must_equal 'Unknown' | |
| validator.valid_luhn.must_equal true | |
| validator.valid.must_equal false | |
| end | |
| it "processes streams correctly" do | |
| inputfile = StringIO.new("4111111111111111\n4111111111111\n4012888888881881\n378282246310005\n6011111111111117\n5105105105105100\n5105 1051 0510 5106\n9111111111111111\n") | |
| outputfile = StringIO.new("VISA: 4111111111111111 (valid)\nVISA: 4111111111111 (invalid)\nVISA: 4012888888881881 (valid)\nAMEX: 378282246310005 (valid)\nDiscover: 6011111111111117 (valid)\nMasterCard: 5105105105105100 (valid)\nMasterCard: 5105105105105106 (invalid)\nUnknown: 9111111111111111 (invalid)\n") | |
| result = CreditCardValidator.process_stream(inputfile) do |line| | |
| line.must_equal outputfile.readline.strip | |
| end | |
| end | |
| end |
| 4111111111111111 | |
| 4111111111111 | |
| 4012888888881881 | |
| 378282246310005 | |
| 6011111111111117 | |
| 5105105105105100 | |
| 5105 1051 0510 5106 | |
| 9111111111111111 |
| #!/usr/bin/env ruby | |
| require_relative 'credit_card_validator' | |
| CreditCardValidator.process_stream(STDIN) do |result| | |
| STDOUT << result << "\n" | |
| end | |