Version 20 of Validating Credit Card Check Digits

Updated 2007-02-16 13:21:36

Sometimes when dealing with e-commerce type applications where a credit card cannot be validated in real-time, it's still useful to make sure the card number's check digit at least checks out. I haven't seen any other Tcl code to do this here on the Wiki, so here's my first contribution.

For a reference on credit card check digit calculations, see http://www.beachnet.com/~hstiles/cardtype.html

-- Michael A. Cleverly


 proc valid_cc {acct} {
     regsub -all -- {[^0-9]} $acct "" acct

     set len [string length $acct]
     if {!([string match 5* $acct]       && $len == 16)                 && \
         !([string match 4* $acct]       && ($len == 13 || $len == 16)) && \
         !([string match {3[47]*} $acct] && $len == 15)                 && \
         !([string match 6011* $acct]    && $len == 16)} {
         return 0
     }

     if {[expr [string length $acct] % 2]} {
         append acct 0
         set odd_factor 1
         set even_factor 2
     } else {
         set odd_factor 2
         set even_factor 1
     }

     foreach {odd even} [split $acct ""] {
         append digits "[expr $odd * $odd_factor][expr $even * $even_factor]"
     }

     set sum 0
     foreach digit [split $digits ""] {
         incr sum $digit
     }

     if {[expr $sum % 10] == 0} {
         return 1
     } else {
         return 0
     }
 } 


 proc card_type {acct} {
     if {[valid_cc $acct]} {
         set len [string length $acct]
         if {[string match 5* $acct] && $len == 16} {
             return mastercard
         } elseif {[string match 4* $acct] && ($len == 13 || $len == 16)} {
             return visa
         } elseif {[string match {3[47]*} $acct] && $len == 15} {
             return amex
         } elseif {[string match 6011* $acct] && $len == 16} {
             return discover
         }
     }
  }

Maybe we will want to add more cards in the future? (I would actually make cards a global in production code)

 proc card_type { acct } {
      set cards {
          mastercard 5     16
          visa       4     13|16
          amex       3[47] 15
          discover   6011  16
      }
      if { [ valid_cc2 $acct ] } {
         regsub -all {[^0-9]} $acct "" acct   ;# [2]
         set len [ string length $acct ]
         foreach { card apat lpat } $cards {
            if { [ regexp ^${apat}.+,($lpat)\$ $acct,$len ] } {
               return $card
            }
         }
      }   ;# [1]
      return invalid
 }

 # proc revised 07.21.02  -- Carl M. Gregory, MC_8 - http://www.cartochka.ru/
 #   [1] Missing a '}'.
 #   [2] Should only worry about 0-9, remove the rest (as does valid_cc2).

Thanks! I implemented the check-digit validation as an exercise, and found to my surprise that my version runs 4 times faster (in tclsh8.4). Not that I expect speed to be critical, but anyway here goes:

 proc valid_cc2 {acct} {
    regsub -all {[^0-9]} $acct "" acct
    set even 0
    set sum 0
    set len [string length $acct]
    while {$len} {
       set new [string index $acct [incr len -1]]
       if {$even} {
          incr new $new
          set new [expr {($new%10)+($new/10)}]
       }
       incr sum $new
       set even [expr {!$even}]
    }
    return [expr {($sum%10) == 0}]
 }

Note that I have omitted here the first part of the above algorithm: I am not checking the correspondence between initial digits and length. The speed increase was measured against the correspondingly reduced valid_cc.

MS


Another exercise in terseness (cf. UIC vehicle number validator), building a string and finally summing all digits (don't know whether it's faster, but it looks more compact):

 proc valid_cc3 {acct} {
        regsub -all {[^0-9]} $acct "" acct
        set even [expr {!([string length $acct]%2)}]
        foreach i  [split $acct ""] {
                if {$even} {incr i $i}
                append t $i
                set even [expr {!$even}]
        }
        expr ([join [split $t ""] +])%10==0
 } ;#RS

You can try an online version here:

http://ats.nist.gov/cgi-bin/cgi.tcl/creditcard.cgi (live)

http://ats.nist.gov/cgi-bin/cgi.tcl/display.cgi?scriptname=creditcard.cgi (source)

Amusingly, if creditcard.cgi finds the check digit doesn't match, it tells you what check digit would make it match!


See also Check digits, CCVS (Credit Card Verification System)


Arts and crafts of Tcl-Tk programming