1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('validators/validator') ;
  9 
 10 /** @class
 11   Validate a field value as a credit card number.
 12 
 13   This validator will perform a basic check to ensure the credit card number
 14   is mathematically valid.  It will also accept numbers with spaces, dashes
 15   or other punctuation.
 16 
 17   Converted credit card numbers are broken into units of 4.
 18 
 19   Basic credit card validation courtesy David Leppek
 20   (https://www.azcode.com/Mod10)
 21 
 22   @extends SC.Validator
 23   @since SproutCore 1.0
 24 */
 25 SC.Validator.CreditCard = SC.Validator.extend(
 26 /** @scope SC.Validator.CreditCard.prototype */ {
 27 
 28   /**
 29     Expects a string of 16 digits.  Will split into groups of 4 for display.
 30   */
 31   fieldValueForObject: function(object, form, field) {
 32     if (typeof(object) == "string" && object.length == 16) {
 33       object = [object.slice(0,4),object.slice(4,8),object.slice(8,12),object.slice(12,16)].join(' ') ;
 34     }
 35     return object ;
 36   },
 37 
 38   /**
 39     Removes all whitespace or dashes to make a single string.
 40   */
 41   objectForFieldValue: function(value, form, field) {
 42     return value.replace(/[\s-\.\:]/g,'') ;
 43   },
 44 
 45   validate: function(form, field) {
 46     return this.checkNumber(field.get('fieldValue')) ;
 47   },
 48 
 49   validateError: function(form, field) {
 50     var label = field.get('errorLabel') || 'Field' ;
 51     return SC.$error(SC.String.loc("Invalid.CreditCard(%@)", label), label);
 52   },
 53 
 54   /**
 55     Allow only numbers, dashes, and spaces
 56   */
 57   validateKeyDown: function(form, field, charStr) {
 58     return !!charStr.match(/[0-9\- ]/);
 59   },
 60 
 61   checkNumber: function(ccNumb) {
 62 
 63     if (!ccNumb || ccNumb.length===0) return YES; // do not validate empty
 64 
 65     // remove any spaces or dashes
 66     ccNumb = ccNumb.replace(/[^0-9]/g,'');
 67 
 68     var valid = "0123456789";  // Valid digits in a credit card number
 69     var len = ccNumb.length;  // The length of the submitted cc number
 70     var iCCN = parseInt(ccNumb,0);  // integer of ccNumb
 71     var sCCN = ccNumb.toString();  // string of ccNumb
 72     sCCN = sCCN.replace (/^\s+|\s+$/g,'');  // strip spaces
 73     var iTotal = 0;  // integer total set at zero
 74     var bNum = true;  // by default assume it is a number
 75     var bResult = false;  // by default assume it is NOT a valid cc
 76     var temp;  // temp variable for parsing string
 77     var calc;  // used for calculation of each digit
 78 
 79     // Determine if the ccNumb is in fact all numbers
 80     for (var j=0; j<len; j++) {
 81       temp = "" + sCCN.substring(j, j+1);
 82       if (valid.indexOf(temp) == "-1"){bNum = false;}
 83     }
 84 
 85     // if it is NOT a number, you can either alert to the fact,
 86     // or just pass a failure
 87     if(!bNum) bResult = false;
 88 
 89     // Determine if it is the proper length
 90     if((len === 0)&&(bResult)){  // nothing, field is blank AND passed above # check
 91       bResult = false;
 92     } else{  // ccNumb is a number and the proper length - let's see if it is a valid card number
 93       if(len >= 15){  // 15 or 16 for Amex or V/MC
 94         for(var i=len;i>0;i--){  // LOOP through the digits of the card
 95           calc = parseInt(iCCN,0) % 10;  // right most digit
 96           calc = parseInt(calc,0);  // assure it is an integer
 97           iTotal += calc;  // running total of the card number as we loop - Do Nothing to first digit
 98           i--;  // decrement the count - move to the next digit in the card
 99           iCCN = iCCN / 10;                               // subtracts right most digit from ccNumb
100           calc = parseInt(iCCN,0) % 10 ;    // NEXT right most digit
101           calc = calc *2;                                 // multiply the digit by two
102           // Instead of some screwy method of converting 16 to a string and then parsing 1 and 6 and then adding them to make 7,
103           // I use a simple switch statement to change the value of calc2 to 7 if 16 is the multiple.
104           switch(calc){
105             case 10: calc = 1; break;       //5*2=10 & 1+0 = 1
106             case 12: calc = 3; break;       //6*2=12 & 1+2 = 3
107             case 14: calc = 5; break;       //7*2=14 & 1+4 = 5
108             case 16: calc = 7; break;       //8*2=16 & 1+6 = 7
109             case 18: calc = 9; break;       //9*2=18 & 1+8 = 9
110             default: calc = calc;           //4*2= 8 &   8 = 8  -same for all lower numbers
111           }
112         iCCN = iCCN / 10;  // subtracts right most digit from ccNum
113         iTotal += calc;  // running total of the card number as we loop
114       }  // END OF LOOP
115       if ((iTotal%10)===0){  // check to see if the sum Mod 10 is zero
116         bResult = true;  // This IS (or could be) a valid credit card number.
117       } else {
118         bResult = false;  // This could NOT be a valid credit card number
119         }
120       }
121     }
122     return bResult; // Return the results
123   }
124 
125 }) ;
126