- added new Discover 65 prefix
authorivan <ivan>
Tue, 19 Dec 2006 07:29:17 +0000 (07:29 +0000)
committerivan <ivan>
Tue, 19 Dec 2006 07:29:17 +0000 (07:29 +0000)
- check for Switch before Visa as Switch has some BINs in ^4
  - accept masked numbers in cardtype()
  - add handling of card network peering arrangements, controllable via
    $Business::CreditCard::Country
- identify Diner's club ^36 cards as MasterCard in US and Canada
  - identify China Union Pay cards as Discover cards outside China
  - identify China Union Pay cards

and... 0.30!

BINS [new file with mode: 0644]
Changes
CreditCard.pm
MANIFEST
test.pl

diff --git a/BINS b/BINS
new file mode 100644 (file)
index 0000000..12475f0
--- /dev/null
+++ b/BINS
@@ -0,0 +1,66 @@
+# from http://perl.about.com/compute/perl/library/nosearch/P073000.htm
+# verified by http://www.beachnet.com/~hstiles/cardtype.html
+# Card Type                         Prefix                           Length
+# MasterCard                        51-55                            16
+# VISA                              4                                13, 16
+# American Express (AMEX)           34, 37                           15
+# Diners Club/Carte Blanche         300-305, 36, 38                  14
+# enRoute                           2014, 2149                       15
+# Discover                          6011                             16
+# JCB                               3                                16
+# JCB                               2131, 1800                       15
+#
+# from Neale Banks <neale@lowendale.com.au>
+# According to a booklet I have from Westpac (an Aussie bank), a card number
+# starting with 5610 or 56022[1-5] is a BankCard
+# BankCards have exactly 16 digits.
+#
+# from "Becker, Max" <Max.Becker@firstgate.com>
+# It's mostly used in the UK and is either called "Switch" or "Solo".
+# Card Type                         Prefix                           Length
+# Switch                            various                          16,18,19
+# Solo                              63, 6767                         16,18,19
+#
+# switch
+#4903
+#    0[2-9]
+#    3[5-9]
+#4911 
+#    0[1-2]
+#    7[4-9]
+#    8[1-2]
+#4936
+#
+# from http://en.wikipedia.org/wiki/Credit_card_number#Prefixes
+# As of November 8, 2004, MasterCard and Diner's club formed an alliance.
+# Cards issued in Canada and the USA start with 55 and are treated as
+# MasterCards worldwide. International cards use the 36 prefix and are
+# treated as mastercards in Canada and the US, but are treated as Diner's
+# Club cards elsewhere. Diner's club international's website makes no
+# reference to old 38 prefix numbers, and they can be presumed reissued under
+# the 55 or 36 BIN prefix.
+#
+# Effective October 1, 2006, the Discover Network will activate new Issuer
+# Identification Numbers (IINs) to support a variety of card types and
+# products. Additionally, Discover and China Union Pay (CUP) have
+# established a strategic alliance and reciprocity agreement. As a result,
+# IIN ranges from CUP will be enabled to facilitate the acceptance of CUP
+# credit Cards on the Discover Network by October 1, 2006. CUP credit
+# cards will be enabled on Discover Network with a 16-digit Card Number
+# only. The updated IIN table is shown below.
+# 
+# Minimum IIN             Maximum IIN             Product
+# 650000          650099          Consumer Debit
+# 650100          650199          Commercial Debit
+# 650200          650399          Stored Value
+# 650400          650599          Stored Value
+# 650600          650799          Consumer Credit
+# 650800          650999          Commercial Credit
+# 651000          659999          Reserved for Future Use
+# 
+#         China Union Pay
+# 62212600                62292599                Credit
+#
+# Please ensure that your POS terminals, websites and any pertinent
+# internal systems can accept these new IINs.
+
diff --git a/Changes b/Changes
index 4e03b3a..63aa43f 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,7 +1,16 @@
 Revision history for Perl extension Business::CreditCard.
 
-0.29  unreleased
+0.30  Mon Dec 18 23:24:25 PST 2006
+        - back after two and a half years; happy hanukkah!
        - added note about B:CC:Object
+       - added new Discover 65 prefix
+       - check for Switch before Visa as Switch has some BINs in ^4
+        - accept masked numbers in cardtype()
+        - add handling of card network peering arrangements, controllable via
+          $Business::CreditCard::Country
+       - identify Diner's club ^36 cards as MasterCard in US and Canada
+        - identify China Union Pay cards as Discover cards outside China
+        - identify China Union Pay cards
 
 0.28  Thu Jul  1 01:17:32 PDT 2004
        - added Switch and Solo cards, patch from Max Becker
index d2b4471..dbc6fea 100644 (file)
@@ -2,7 +2,9 @@ package Business::CreditCard;
 
 # Jon Orwant, <orwant@media.mit.edu>
 #
-# Copyright 1995,1996,1997 Jon Orwant.  All rights reserved.
+# Copyright 1995,1996,1997 Jon Orwant
+# Copyright 2001-2006 Ivan Kohler
+# All rights reserved.
 # This program is free software; you can redistribute it and/or
 # modify it under the same terms as Perl itself.
 #
@@ -12,11 +14,13 @@ package Business::CreditCard;
 require 5;
 
 require Exporter;
-use vars qw( @ISA $VERSION );
+use vars qw( @ISA $VERSION $Country );
 
 @ISA = qw( Exporter );
 
-$VERSION = "0.29";
+$VERSION = "0.30";
+
+$Country = 'US';
 
 =head1 NAME
 
@@ -42,7 +46,8 @@ The validate() subroutine returns 1 if the card number provided passes
 the checksum test, and 0 otherwise.
 
 The cardtype() subroutine returns a string containing the type of
-card.  My list is not complete; I welcome additions.
+card.  The list of possible return values is more comprehensive than it used
+to be, but additions are still most welcome.
 
 Possible return values are:
 
@@ -56,10 +61,18 @@ Possible return values are:
   BankCard
   Switch
   Solo
+  China Union Pay
   Unknown
 
-"Not a credit card" is returned on obviously invalid
-data values.
+"Not a credit card" is returned on obviously invalid data values.
+
+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;
+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
+all cards including the remaining Switch cards, you need the first six
+digits.
 
 The generate_last_digit() subroutine computes and returns the last
 digit of the card given the preceding digits.  With a 16-digit card,
@@ -67,17 +80,40 @@ you provide the first 15 digits; the subroutine returns the sixteenth.
 
 This module does I<not> tell you whether the number is on an actual
 card, only whether it might conceivably be on a real card.  To verify
-whether a card is real, or whether it's been stolen, or what its
-balance is, you need a Merchant ID, which gives you access to credit
-card databases.  The Perl Journal (http://tpj.com/tpj) has
-a Merchant ID so that I can accept MasterCard and VISA payments; it
-comes with the little pushbutton/slide-your-card-through device you've
-seen in restaurants and stores.  That device calculates the checksum
-for you, so I don't actually use this module.
+whether a card is real, or whether it's been stolen, or to actually process
+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
+
+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.
+
+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
+is probably what you want to determine if you accept the card, or which
+merchant agreement is is processed through.
+
+You can also set C<$Business::OnlinePayment::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.
+
+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 China Union Pay cards are identified as Discover cards outside China.
+
+=back
+
+=item 
+
 =head1 AUTHOR
 
 Jon Orwant
@@ -99,6 +135,9 @@ L<Business::CreditCard::Object> is a wrapper around Business::CreditCard
 providing an OO interface.  Assistance integrating this into the base
 Business::CreditCard distribution is welcome.
 
+L<Business::OnlinePayment> is a framework for processing online payments
+including modules for various payment gateways.
+
 =cut
 
 @EXPORT = qw(cardtype validate generate_last_digit);
@@ -106,53 +145,51 @@ Business::CreditCard distribution is welcome.
 sub cardtype {
     my ($number) = @_;
 
-    return "Not a credit card" if $number =~ /[^\d\s]/;
+    $number =~ s/[\s\-]//go;
+    $number =~ s/[x\*\.\_]/x/gio;
 
-    $number =~ s/\D//g;
+    return "Not a credit card" if $number =~ /[^\dx]/io;
+
+    #$number =~ s/\D//g;
 
     return "Not a credit card" unless length($number) >= 13 && 0+$number;
 
-    return "VISA card" if $number =~ /^4\d{12}(\d{3})?$/o;
-    return "MasterCard" if $number =~ /^5[1-5]\d{14}$/o;
-    return "Discover card" if $number =~ /^6011\d{12}$/o;
-    return "American Express card" if $number =~ /^3[47]\d{13}$/o;
-    return "Diner's Club/Carte Blanche"
-      if $number =~ /^3(0[0-5]|[68]\d)\d{11}$/o;
-    return "enRoute" if $number =~ /^2(014|149)\d{11}$/o;
-    return "JCB" if $number =~ /^(3\d{4}|2131|1800)\d{11}$/o;
-    return "BankCard" if $number =~ /^56(10\d\d|022[1-5])\d{10}$/o;
     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})\d{10}(\d{2,3})?$/o
-      || $number =~ /^564182\d{10}(\d{2,3})?$/o
-      || $number =~ /^6(3(33[0-4][0-9])|759[0-9]{2})\d{10}(\d{2,3})?$/o;
+      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
+      || $number =~ /^564182[\dx]{10}([\dx]{2,3})?$/o
+      || $number =~ /^6(3(33[0-4][0-9])|759[0-9]{2})[\dx]{10}([\dx]{2,3})?$/o;
+
+    return "VISA card" if $number =~ /^4[\dx]{12}([\dx]{3})?$/o;
+
+    return "MasterCard"
+      if   $number =~ /^5[1-5][\dx]{14}$/o
+      || ( $number =~ /^36[\dx]{12}/ && $Country =~ /^(US|CA)$/oi );
+
+    return "Discover card"
+      if   $number =~ /^6011[\dx]{12}$/o
+      ||   $number =~ /^65[\dx]{14}$/o
+      || ( $number =~ /^622[\dx]{13}$/o && $Country !~ /^(CN)$/oi );
+
+    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 "enRoute" if $number =~ /^2(014|149)[\dx]{11}$/o;
+
+    return "JCB" if $number =~ /^(3[\dx]{4}|2131|1800)[\dx]{11}$/o;
+
+    return "BankCard" if $number =~ /^56(10[\dx][\dx]|022[1-5])[\dx]{10}$/o;
+
     return "Solo"
-      if $number =~ /^6(3(34[5-9][0-9])|767[0-9]{2})\d{10}(\d{2,3})?$/o;
+      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;
+
     return "Unknown";
 }
 
-# from http://perl.about.com/compute/perl/library/nosearch/P073000.htm
-# verified by http://www.beachnet.com/~hstiles/cardtype.html
-# Card Type                         Prefix                           Length
-# MasterCard                        51-55                            16
-# VISA                              4                                13, 16
-# American Express (AMEX)           34, 37                           15
-# Diners Club/Carte Blanche         300-305, 36, 38                  14
-# enRoute                           2014, 2149                       15
-# Discover                          6011                             16
-# JCB                               3                                16
-# JCB                               2131, 1800                       15
-#
-# from Neale Banks <neale@lowendale.com.au>
-# According to a booklet I have from Westpac (an Aussie bank), a card number
-# starting with 5610 or 56022[1-5] is a BankCard
-# BankCards have exactly 16 digits.
-#
-# from "Becker, Max" <Max.Becker@firstgate.com>
-# It's mostly used in the UK and is either called "Switch" or "Solo".
-# Card Type                         Prefix                           Length
-# Switch                            various                          16,18,19
-# Solo                              63, 6767                         16,18,19
-
 sub generate_last_digit {
     my ($number) = @_;
     my ($i, $sum, $weight);
index a1a51be..907347f 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -4,3 +4,4 @@ MANIFEST
 Makefile.PL
 test.pl
 README
+BINS
diff --git a/test.pl b/test.pl
index f96a1d2..72dd975 100644 (file)
--- a/test.pl
+++ b/test.pl
@@ -36,7 +36,8 @@ sub test_card_identification{
                 '371234567890123' =>    'American Express card',
                 '30112345678901' =>     "Diner's Club/Carte Blanche",
                 '30512345678901' =>     "Diner's Club/Carte Blanche",
-                '36123456789012' =>     "Diner's Club/Carte Blanche",
+                #'36123456789012' =>     "Diner's Club/Carte Blanche",
+                '36123456789012' =>     'MasterCard',
                 '38123456789012' =>     "Diner's Club/Carte Blanche",
                 '201412345678901' =>    'enRoute',
                 '214912345678901' =>    'enRoute',
@@ -46,6 +47,9 @@ sub test_card_identification{
                 '180012345678901' =>    'JCB',
                 '1800123456789012' =>   'Unknown',
                 '312345678901234' =>    'Unknown',
+                '4111xxxxxxxxxxxx' =>   'VISA card',
+                '6599xxxxxxxxxxxx' =>   'Discover card',
+                '6222xxxxxxxxxxxx' =>   'Discover card', #China Union Pay
         );
         while( my ($k, $v)=each(%test_table) ){
                 if(cardtype($k) ne $v){