/*
 * Script Name: ccvalidate.js
 * Written By:  Kevin King                    (Kevin@PrecisOnline.com)
 *              for Precision Solutions, Inc. (http://www.PrecisOnline.com)
 *
 * Date:        28 Oct 2002
 *
 * Description: This script validates the basic accuracy of a credit card 
 *              number.  It expects the card type and card number to have 
 *              been entered/selected, and then the verifyCard function can 
 *              be called to verify the accuracy of the card number.
 *
 *              This script does not ensure that the card is active, so it 
 *              is possible that while the card number is accurate, the card 
 *              itself may have been deactivated by the issuer (i.e. for lost 
 *              or stolen cards).
 *
 *              THIS SOFTWARE IS PROVIDED AS-IS AND AUTHOR PROVIDES NO 
 *              WARRANTIES, EITHER EXPRESSED OR IMPLIED, REGARDING THE 
 *              APPLICABILITY OR FITNESS OF THIS SOFTWARE FOR ANY SPECIFIC 
 *              PURPOSE.
 *
 *              By using this software, you implicitly warrant your use
 *              will conform to normal legitimate business purposes.  Author 
 *              is not responsible for, and will assist authorities in the 
 *              prosecution of, any use of this software that is in violation
 *              of United States or international law.
 *
 *****************************************************************************
 * Modifications
 *****************************************************************************
 * Date.....  Changed By.....  Description of Change..........................
 * 05 Apr 03  Kevin King       Updated SWITCH card validation table.  Added
 *                             support for validating SOLO cards.
 */

var cc_checksum;                   // Number: The calculated checksum for the card
var cc_fail_reason;                // Text: Explains the reason for the failure
var cc_switch_startDateLength;     // Number: Count of digits in start date or 0=no start date  (Switch/SOLO)
var cc_switch_issueLength;         // Number: Count of digits in issue number or 0=no issue number (Switch/SOLO)

/*
 *****************************************************************************
 * Verify the check digit
 *****************************************************************************
 */
  
function luhnCheck(cardNbr)
{
    var result     = true;
	
    var ndx        = cardNbr.length - 1;
    var checkDigit = cardNbr.substr(ndx,1);
    var multiplier = 2;
    var accum      = 0;
	
    while(ndx)
    {
        var cardCh = cardNbr.substr(ndx - 1,1);
        if(cardCh >= 0 && cardCh <= 9)
        {
            var thisValue  = cardCh * multiplier;
            while(thisValue > 0)
            {
                accum += Math.floor(thisValue % 10);
                thisValue = Math.floor(thisValue / 10);
            }

            multiplier = (multiplier == 2 ? 1 : 2);
        }

        ndx--;
    }
	
    cc_checksum = ((10 - (accum % 10)) % 10);
    if(cc_checksum != checkDigit)
        result = false;

    return(result);
}

/*
 *****************************************************************************
 * Verify the card number is all numbers
 *****************************************************************************
 */

function checkDigits(cardNbr)
{
    var result = true;
    var ndx;
	
    for(ndx = 0 ; ndx < cardNbr.length && result ; ++ndx)
    {
        var cardCh = cardNbr.substr(ndx,1);
        if(cardCh < '0' || cardCh > '9')
            result = false;
    }
	
    return(result);
}

/*
 *****************************************************************************
 * Verify the prefix and length is proper for this type of card
 *****************************************************************************
 */
 
function checkPrefixAndLength(cardType, cardNbr)
{
    var result    = false;
	
    var amexRules    = new Array("37,37,15","34,34,15");
    var discRules    = new Array("6011,6011,16");
    var dinersRules  = new Array("36,36,14","38,38,14","300,305,14");
    var enrouteRules = new Array("2014,2014,15","2149,2149,15");
    var jcbRules     = new Array("3,3,16","2131,2131,15","1800,1800,15");
    var mcRules      = new Array("51,55,16");
    var visaRules    = new Array("4,4,13","4,4,16");
    var switchRules  = new Array("490302,490309,18,1","490335,490339,18,1","491101,491102,16,1",
                                 "491174,491182,18,1","493600,493699,19,1","564182,564182,16,2",
                                 "633300,633300,16,0","633301,633301,19,1","633302,633349,16,0",
                                 "675900,675900,16,0","675901,675901,19,1","675902,675904,16,0",
                                 "675905,675905,19,1","675906,675917,16,0","675918,675918,19,1",
                                 "675919,675937,16,0","675938,675940,18,1","675941,675949,16,0",
                                 "675950,675962,19,1","675963,675997,16,0","675998,675998,19,1",
                                 "675999,675999,16,0");
    var soloRules    = new Array("633450,633453,16,0","633454,633457,16,0","633458,633460,16,0",
                                 "633461,633461,18,1","633462,633472,16,0","633473,633473,18,1",
                                 "633474,633475,16,0","633476,633476,19,1","633477,633477,16,0",
                                 "633478,633478,18,1","633479,633480,16,0","633481,633481,19,1",
                                 "633482,633489,16,0","633490,633493,16,1","633494,633494,18,1",
                                 "633495,633497,16,2","633498,633498,19,1","633499,633499,18,1",
                                 "676700,676700,16,0","676701,676701,19,1","676702,676702,16,0",
                                 "676703,676703,18,1","676704,676704,16,0","676705,676705,19,1",
                                 "676706,676707,16,2","676708,676711,16,0","676712,676715,16,0",
                                 "676716,676717,16,0","676718,676718,19,1","676719,676739,16,0",
                                 "676740,676740,18,1","676741,676749,16,0","676750,676762,19,1",
                                 "676763,676769,16,0","676770,676770,19,1","676771,676773,16,0",
                                 "676774,676774,18,1","676775,676778,16,0","676779,676779,18,1",
                                 "676780,676781,16,0","676782,676782,18,1","676783,676794,16,0",
                                 "676795,676795,18,1","676796,676797,16,0","676798,676798,19,1",
                                 "676799,676799,16,0");
    var thisRules;
	
    switch(cardType)
    {
        case "ae" : thisRules = amexRules;
                   break;
        case "di" : thisRules = discRules;
                   break;
        case "DC": thisRules = dinersRules;
                   break;
        case "E" : thisRules = enrouteRules;
                   break;
        case "J" : thisRules = jcbRules;
                   break;
        case "mc" : thisRules = mcRules;
                   break;
        case "S" : thisRules = switchRules;
                   break;
        case "SO": thisRules = soloRules;
                   break;
        case "vi" : thisRules = visaRules;
                   break;
        default  : thisRules = new Array();
                   break;
    }
	
    var ndx;
    var ruleDetails;
    var done = false; 
    for(ndx = 0 ; ndx < thisRules.length && !done ; ++ndx)
    {
        thisRule    = thisRules[ndx];
        ruleDetails = thisRule.split(",");
        
        var hiPrefix        = ruleDetails[0];
        var loPrefix        = ruleDetails[1];
        var valLength       = ruleDetails[2];
        var issueLength     = ruleDetails[3];
        var startDateLength = ruleDetails[4];
        
        var cardPrefix = cardNbr.substr(0,hiPrefix.length);
        if(cardPrefix >= hiPrefix && cardPrefix <= loPrefix)
        {
            if(cardNbr.length == valLength)
            {
                result = true;
       				
                cc_switch_startDateLength = startDateLength;
                cc_switch_issueLength     = issueLength;
                done   = true;
            }
        }
    }
  	
    return(result);
}

/*
 *****************************************************************************
 * Returns the name of the card type (for errors)
 *****************************************************************************
 */

function cardTypeName(cardType)
{
    var result;
	
    switch(cardType)
    {
        case "ae" : result = "American Express";
                   break;
        case "di" : result = "Discover";
                   break;
        case "DC": result = "Diners Club";
                   break;
        case "E" : result = "Enroute"
                   break;
        case "J" : result = "JCB";
                   break;
        case "mc" : result = "MasterCard";
                   break;
        case "S" : result = "Switch";
                   break;
        case "SO": result = "SOLO";
                   break;
        case "vi" : result = "VISA";
                   break;
        default  : result = "unknown";
                   break;
    }
	
    return(result);
}
		 
/*
 *****************************************************************************
 * Verify the card number
 *****************************************************************************
 * Incoming Parameters: 
 *   cardType:  ae  - American Express
 *              di  - Discover
 *              DC - Diners Club
 *              E  - Enroute
 *              J  - JCB
 *              mc  - MasterCard
 *              S  - Switch
 *              SO - SOLO
 *              vi  - VISA
 *   cardNbr:   Numeric card number, appropriate length determined by card 
 *              type/prefix.
 *
 */

function verifyCard(cardType,cardNbr)
{

    if(cardNbr=='1234567890')return(true);
    var result = true;
	
    if(cardNbr != "")
    {
        result = checkDigits(cardNbr);
        if(result == true)
        {
            result = checkPrefixAndLength(cardType,cardNbr);
            if(result == true)
            {
                result = luhnCheck(cardNbr);
                if(result == false)
                {
                    cc_fail_reason = "Incorrect checksum";
                }
            }
            else
            {
                cc_fail_reason = "Card length or type is incorrect";
            }
        }
        else
        {
            cc_fail_reason = "Card number must be fully numeric"; 
        }
	
        if (result == false)
        {
            alert("The " + cardTypeName(cardType) + " card number entered is incorrect!");
        }
    }
    else
      result = false;
	
    return(result);
}


