Add link to Neil Bowers' review of CC check modules
[Business-CreditCard.git] / CreditCard.pm
index 426af19..d657e42 100644 (file)
@@ -5,7 +5,7 @@ use vars qw( @ISA $VERSION $Country );
 
 @ISA = qw( Exporter );
 
-$VERSION = "0.31";
+$VERSION = "0.32_01";
 
 $Country = 'US';
 
@@ -42,19 +42,23 @@ Possible return values are:
   MasterCard
   Discover card
   American Express card
-  Diner's Club/Carte Blanche
   enRoute
   JCB
   BankCard
   Switch
   Solo
   China Union Pay
+  Laser
+  Isracard
   Unknown
 
 "Not a credit card" is returned on obviously invalid data values.
 
+Versions before 0.31 may also have returned "Diner's Club/Carte Blanche" (these
+cards are now recognized as "Discover card").
+
 As of 0.30, cardtype() will accept a partial card masked with "x", "X', ".",
-"*" or "_".  Only the first 2-6 digits and the lenth are significant;
+"*" or "_".  Only the first 2-6 digits and the length are significant;
 whitespace and dashes are removed.  To recognize just Visa, MasterCard and
 Amex, you only need the first two digits; to recognize almost all cards
 except some Switch cards, you need the first four digits, and to recognize
@@ -73,7 +77,7 @@ charges, you need a Merchant account.  See L<Business::OnlinePayment>.
 These subroutines will also work if you provide the arguments
 as numbers instead of strings, e.g. C<validate(5276440065421319)>.  
 
-=head1 CHANGES IN 0.30
+=head1 PROCESSING AGREEMENTS
 
 Credit card issuers have recently been forming agreements to process cards on
 other networks, in which one type of card is processed as another card type.
@@ -81,11 +85,11 @@ other networks, in which one type of card is processed as another card type.
 By default, Business::CreditCard returns the type the card should be treated as
 in the US and Canada.  You can change this to return the type the card should
 be treated as in a different country by setting
-C<$Business::OnlinePayment::Country> to your two-letter country code.  This
+C<$Business::CreditCard::Country> to your two-letter country code.  This
 is probably what you want to determine if you accept the card, or which
-merchant agreement is is processed through.
+merchant agreement it is processed through.
 
-You can also set C<$Business::OnlinePayment::Country> to a false value such
+You can also set C<$Business::CreditCard::Country> to a false value such
 as the empty string to return the "base" card type.  This is probably only
 useful for informational purposes when used along with the default type.
 
@@ -93,13 +97,19 @@ Here are the currently known agreements:
 
 =over 4
 
-=item Diner's club cards (starting with 36) are now identified as "MasterCard" inside the US and Canada.
+=item Most Diner's club is now identified as Discover.  (This supercedes the earlier identification of some Diner's club cards as MasterCard inside the US and Canada.)
+
+=item JCB cards in the 3528-3589 range are identified as Discover inside the US and Canada.
 
 =item China Union Pay cards are identified as Discover cards outside China.
 
 =back
 
-=item 
+=head1 NOTE ON INTENDED PURPOSE
+
+This module is for verifying I<real world> B<credit cards>.  It is B<NOT> a
+pedantic implementation of the ISO 7812 standard, a general-purpose LUHN
+implementation, or intended for use with "creditcard-like account numbers".
 
 =head1 AUTHOR
 
@@ -114,18 +124,31 @@ Please don't bother Jon with emails about this module.
 
 Lee Lawrence <LeeL@aspin.co.uk>, Neale Banks <neale@lowendale.com.au> and
 Max Becker <Max.Becker@firstgate.com> contributed support for additional card
-types.  Lee also contributed a working test.pl.
+types.  Lee also contributed a working test.pl.  Alexandr Ciornii
+<alexchorny@gmail.com> contributed code cleanups.
 
 =head1 COPYRIGHT AND LICENSE
 
 Copyright (C) 1995,1996,1997 Jon Orwant
 Copyright (C) 2001-2006 Ivan Kohler
-Copyright (C) 2007 Freeside Internet Services, Inc.
+Copyright (C) 2007-2012 Freeside Internet Services, Inc.
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself, either Perl version 5.8.8 or,
 at your option, any later version of Perl 5 you may have available.
 
+=head1 BUGS
+
+(paraphrasing Neil Bowers) We export all functions by default.  It would be
+better to let the user decide which functions to import.  And validate() is
+a bit of a generic name.
+
+The question is, after almost 2 decades with this interface (inherited from
+the original author, who probably never expected it to live half this long),
+how to change things to behave in a more modern fashion without breaking
+existing code?  "use Business::CreditCard <some_minimum_version>" turns it off?
+Explicitly ask to turn it off and list that in the SYNOPSIS?
+
 =head1 SEE ALSO
 
 L<Business::CreditCard::Object> is a wrapper around Business::CreditCard
@@ -135,6 +158,9 @@ Business::CreditCard distribution is welcome.
 L<Business::OnlinePayment> is a framework for processing online payments
 including modules for various payment gateways.
 
+http://neilb.org/reviews/luhn.html is an excellent overview of similar modules
+providing credit card number verification (LUHN checking).
+
 =cut
 
 @EXPORT = qw(cardtype validate generate_last_digit);
@@ -148,8 +174,14 @@ sub cardtype {
     return "Not a credit card" if $number =~ /[^\dx]/io;
 
     #$number =~ s/\D//g;
-
-    return "Not a credit card" unless length($number) >= 13 && 0+$number;
+    {
+      local $^W=0; #no warning at next line
+      return "Not a credit card"
+        unless ( length($number) >= 13
+                 || length($number) == 8 || length($number) == 9 #Isracard
+               )
+            && 0+$number;
+    }
 
     return "Switch"
       if $number =~ /^49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})[\dx]{10}([\dx]{2,3})?$/o
@@ -160,18 +192,24 @@ sub cardtype {
 
     return "MasterCard"
       if   $number =~ /^5[1-5][\dx]{14}$/o
-      || ( $number =~ /^36[\dx]{12}/ && $Country =~ /^(US|CA)$/oi );
+      ;# || ( $number =~ /^36[\dx]{12}/ && $Country =~ /^(US|CA)$/oi );
 
     return "Discover card"
-      if   $number =~ /^6011[\dx]{12}$/o
+      if   $number =~ /^30[0-5][\dx]{11}([\dx]{2})?$/o  #diner's: 300-305
+      ||   $number =~ /^3095[\dx]{10}([\dx]{2})?$/o     #diner's: 3095
+      ||   $number =~ /^3[68][\dx]{12}([\dx]{2})?$/o    #diner's: 36
+      ||   $number =~ /^6011[\dx]{12}$/o
+      ||   $number =~ /^64[4-9][\dx]{13}$/o
       ||   $number =~ /^65[\dx]{14}$/o
-      || ( $number =~ /^622[\dx]{13}$/o && $Country !~ /^(CN)$/oi );
+      || ( $number =~ /^62[24-68][\dx]{13}$/o && uc($Country) ne 'CN' ) #CUP
+      || ( $number =~ /^35(2[89]|[3-8][\dx])[\dx]{10}$/o && uc($Country) eq 'US' );
 
     return "American Express card" if $number =~ /^3[47][\dx]{13}$/o;
 
-    return "Diner's Club/Carte Blanche"
-      if $number =~ /^3(0[0-5]|[68][\dx])[\dx]{11}$/o;
+    #return "Diner's Club/Carte Blanche"
+    #  if $number =~ /^3(0[0-59]|[68][\dx])[\dx]{11}$/o;
 
+    #"Diners Club enRoute"
     return "enRoute" if $number =~ /^2(014|149)[\dx]{11}$/o;
 
     return "JCB" if $number =~ /^(3[\dx]{4}|2131|1800)[\dx]{11}$/o;
@@ -182,13 +220,22 @@ sub cardtype {
       if $number =~ /^6(3(34[5-9][0-9])|767[0-9]{2})[\dx]{10}([\dx]{2,3})?$/o;
 
     return "China Union Pay"
-      if $number =~ /^622[\dx]{13}$/o;
+      if $number =~ /^62[24-68][\dx]{13}$/o;
+
+    return "Laser"
+      if $number =~ /^6(304|7(06|09|71))[\dx]{12,15}$/o;
+
+    return "Isracard"
+      if $number =~ /^[\dx]{8,9}$/;
 
     return "Unknown";
 }
 
 sub generate_last_digit {
     my ($number) = @_;
+
+    die "invalid operation" if length($number) == 8 || length($number) == 9;
+
     my ($i, $sum, $weight);
 
     $number =~ s/\D//g;
@@ -203,12 +250,22 @@ sub generate_last_digit {
 
 sub validate {
     my ($number) = @_;
+
     my ($i, $sum, $weight);
     
     return 0 if $number =~ /[^\d\s]/;
 
     $number =~ s/\D//g;
 
+    if ( $number =~ /^[\dx]{8,9}$/ ) { # Isracard
+        $number = "0$number" if length($number) == 8;
+        for($i=1;$i<length($number);$i++){
+            $sum += substr($number,9-$i,1) * $i;
+        }
+        return 1 if $sum%11 == 0;
+        return 0;
+    }
+
     return 0 unless length($number) >= 13 && 0+$number;
 
     for ($i = 0; $i < length($number) - 1; $i++) {