Credit Card Verification as an exercise

Couldn't find any credit card verification code written in ruby so I wrote up my own. This code turns out a lot smaller than the perl version I have been using. I thought it was an interesting exercise, here is how its done, if anybody can improve on what I've done I'd be impressed.

Heres a howto guide on it
http://www.beachnet.com/~hstiles/cardtype.html

Feel free to use the code.

class CreditCard < ActiveRecord::Base

  def before_save
    if number_is_valid && number_matches_type
       ...
    end
  end

  def number_is_valid
    total = 0
    number.gsub(/[^0-9]/, '').reverse.scan(/(\d)(\d){0,1}/) do |ud,ad|
      (ad.to_i*2).to_s.each {|d| total = total + d.to_i} if ad
      total = total + ud.to_i
    end
    total % 10 == 0 ? false : true
  end

  def number_matches_type
    digit_length = number.length
    if card_type == 'visa'
      if (digit_length == 16) || (digit_length == 13)
        return true if number[/^\d/].to_i == 4
      end
    elsif card_type == 'mastercard'
      if digit_length == 16
        return true if (51..55).to_a.include?(number[0..1].to_i)
      end
    elsif card_type == 'amex'
      if digit_length == 15
        return true if [34,37].include?(number[0..1].to_i)
      end
    elsif card_type == 'discover'
      if digit_length == 16
        return true if number[0..3].to_i == 6011
      end
    end
  end
end

Jeffrey Moss wrote:

Couldn't find any credit card verification code written in ruby so I
wrote up my own. This code turns out a lot smaller than the perl
version I have been using. I thought it was an interesting exercise,
here is how its done, if anybody can improve on what I've done I'd be
impressed. %*

Nice code and I guess it will be quite useful as well! Thanks for posting this here.

class CreditCard < ActiveRecord::Base

  def before_save
    if number_is_valid && number_matches_type
       ...
    end
  end

  def number_is_valid

Guess, I'd rename this to 'number_valid?'. :slight_smile:

    total = 0
    number.gsub(/[^0-9]/, '').reverse.scan(/(\d)(\d){0,1}/) do |ud,ad|
      (ad.to_i*2).to_s.each {|d| total = total + d.to_i} if ad
      total = total + ud.to_i
    end
    total % 10 == 0 ? false : true

Hm, why not simply "total % 10 != 0"?

  end

  def number_matches_type

Might be nice to append a question mark here as well.

    digit_length = number.length
    if card_type == 'visa'
      if (digit_length == 16) || (digit_length == 13)
        return true if number[/^\d/].to_i == 4
      end
    elsif card_type == 'mastercard'
      if digit_length == 16
        return true if (51..55).to_a.include?(number[0..1].to_i)

Hmmm, why the .to_a call?

      end
    elsif card_type == 'amex'
      if digit_length == 15
        return true if [34,37].include?(number[0..1].to_i)
      end
    elsif card_type == 'discover'
      if digit_length == 16
        return true if number[0..3].to_i == 6011
      end
    end
  end
end

I think all this returns are superfluous -- after all Ruby will return the result of evaluating the matching if clause.

Oh, and I think this is a good case for the case construct:

case card_type
   when 'visa' then
     [13, 16].include?(digit_length) and number[0, 1] == "4"
   when 'mastercard' then
     digit_length == 16 and ("51" .. "55").include?(number[0, 2])
   when 'amex' then
     digit_length == 15 and %w(34 37).include?(number[0, 2])
   when 'discover' then
     digit_length == 16 and number[0, 4] == "6011"
end

Jeffrey Moss wrote:

Couldn't find any credit card verification code written in ruby so I

wrote up my own.

There is this: http://rubyforge.org/projects/creditcard

free your mind neo

right, there is no spoon

yeah

···

----- Original Message ----- From: "Florian Gross" <flgr@ccan.de>
Newsgroups: comp.lang.ruby
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Wednesday, March 23, 2005 4:34 PM
Subject: Re: Credit Card Verification as an exercise

Jeffrey Moss wrote:

Couldn't find any credit card verification code written in ruby so I
wrote up my own. This code turns out a lot smaller than the perl
version I have been using. I thought it was an interesting exercise,
here is how its done, if anybody can improve on what I've done I'd be
impressed. %*

Nice code and I guess it will be quite useful as well! Thanks for posting this here.

class CreditCard < ActiveRecord::Base

  def before_save
    if number_is_valid && number_matches_type
       ...
    end
  end

  def number_is_valid

Guess, I'd rename this to 'number_valid?'. :slight_smile:

    total = 0
    number.gsub(/[^0-9]/, '').reverse.scan(/(\d)(\d){0,1}/) do |ud,ad|
      (ad.to_i*2).to_s.each {|d| total = total + d.to_i} if ad
      total = total + ud.to_i
    end
    total % 10 == 0 ? false : true

Hm, why not simply "total % 10 != 0"?

  end

  def number_matches_type

Might be nice to append a question mark here as well.

    digit_length = number.length
    if card_type == 'visa'
      if (digit_length == 16) || (digit_length == 13)
        return true if number[/^\d/].to_i == 4
      end
    elsif card_type == 'mastercard'
      if digit_length == 16
        return true if (51..55).to_a.include?(number[0..1].to_i)

Hmmm, why the .to_a call?

      end
    elsif card_type == 'amex'
      if digit_length == 15
        return true if [34,37].include?(number[0..1].to_i)
      end
    elsif card_type == 'discover'
      if digit_length == 16
        return true if number[0..3].to_i == 6011
      end
    end
  end
end

I think all this returns are superfluous -- after all Ruby will return the result of evaluating the matching if clause.

Oh, and I think this is a good case for the case construct:

case card_type
  when 'visa' then
    [13, 16].include?(digit_length) and number[0, 1] == "4"
  when 'mastercard' then
    digit_length == 16 and ("51" .. "55").include?(number[0, 2])
  when 'amex' then
    digit_length == 15 and %w(34 37).include?(number[0, 2])
  when 'discover' then
    digit_length == 16 and number[0, 4] == "6011"
end

Florian Gross wrote:

case card_type
  when 'visa' then
    [13, 16].include?(digit_length) and number[0, 1] == "4"
  when 'mastercard' then
    digit_length == 16 and ("51" .. "55").include?(number[0, 2])
  when 'amex' then
    digit_length == 15 and %w(34 37).include?(number[0, 2])
  when 'discover' then
    digit_length == 16 and number[0, 4] == "6011"
end

Actually, you don't need the "then"s either (and you should probably have an else, even though nil is "untrue" as well)

case card_type
when 'visa'
   [13, 16].include?(digit_length) and number[0] == ?4
when 'mastercard'
   digit_length == 16 and ("51" .. "55").include?(number[0, 2])
when 'amex'
   digit_length == 15 and %w(34 37).include?(number[0, 2])
when 'discover'
   digit_length == 16 and number[0, 4] == "6011"
else
   false
end

Ben

Ben Giddings wrote:

Florian Gross wrote:

case card_type
  when 'visa' then
    [13, 16].include?(digit_length) and number[0, 1] == "4"
  when 'mastercard' then
    digit_length == 16 and ("51" .. "55").include?(number[0, 2])
  when 'amex' then
    digit_length == 15 and %w(34 37).include?(number[0, 2])
  when 'discover' then
    digit_length == 16 and number[0, 4] == "6011"
end

Actually, you don't need the "then"s either (and you should probably have an else, even though nil is "untrue" as well)

I know, but I somehow think it looks more balanced that way. Though I was not sure whether to use "then" or ":" in this case -- I guess it is a matter of taste and not too important. :slight_smile:

Jeffrey Moss wrote:

free your mind neo

right, there is no spoon

yeah

Hm, I get the reference, but I'm not sure I understand what you're trying to hint at -- was I being too hard to understand? Sorry in that case. I'd be pleased to clear it up, if you can come up with concrete questions.

Oh, and sorry if I'm misunderstanding.

They were good suggestions, I haven't quite adjusted to ruby, still in a perl mindset. That's what I meant. I'm trying to free my mind. The perl code was pretty long, maybe over 100 lines, in ruby its like 20, heh heh.

I leave the "then" off too, I only use then if its a one liner, and in that case I will usually use { }'s
if there is a CR right after the if line, then its self explanatory I think (and emacs indents the line after so its all good)
minor details

-Jeff

···

----- Original Message ----- From: "Florian Gross" <flgr@ccan.de>
Newsgroups: comp.lang.ruby
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Wednesday, March 23, 2005 5:39 PM
Subject: Re: Credit Card Verification as an exercise

Jeffrey Moss wrote:

free your mind neo

right, there is no spoon

yeah

Hm, I get the reference, but I'm not sure I understand what you're trying to hint at -- was I being too hard to understand? Sorry in that case. I'd be pleased to clear it up, if you can come up with concrete questions.

Oh, and sorry if I'm misunderstanding.

Jeffrey Moss wrote:

They were good suggestions, I haven't quite adjusted to ruby, still in a perl mindset. That's what I meant. I'm trying to free my mind. The perl code was pretty long, maybe over 100 lines, in ruby its like 20, heh heh.

Heh, that's one reason we love it so much. :slight_smile:

I leave the "then" off too, I only use then if its a one liner, and in that case I will usually use { }'s
if there is a CR right after the if line, then its self explanatory I think (and emacs indents the line after so its all good)
minor details

I guess my habit comes from writing such code:

if items.all? do |item|
   item > 5 and
   more complex checks and
   so on or
   whatever
end then
   do something
end

That was the reason for me generally adapting the 'then' suffixes, I think.