001    package railo.runtime.op.validators;
002    
003    import railo.commons.lang.StringUtil;
004    import railo.runtime.exp.ExpressionException;
005    import railo.runtime.op.Caster;
006    
007    /**
008     * logic to determine if a credit card number is valid. no GUI, just
009     * calculation.
010     *
011     */
012    public final class ValidateCreditCard {
013    
014        /**
015         * enum for Amex
016         */
017        static final int AMEX = 1;
018    
019        /**
020         * true if debugging output wanted
021         */
022        static final boolean DEBUGGING = true;
023    
024        /**
025         * enum for Diner's club
026         */
027        static final int DINERS = 2; // includes Carte Blanche
028    
029        /**
030         * enum for Discover Card
031         */
032        static final int DISCOVER = 3;
033    
034        /**
035         * enum for Enroute
036         */
037        static final int ENROUTE = 4;
038    
039        /**
040         * enum for JCB
041         */
042        static final int JCB = 5;
043    
044        /**
045         * enum for Mastercard
046         */
047        static final int MASTERCARD = 6;
048    
049        /**
050         * enum for insufficient digits
051         */
052        static final int NOT_ENOUGH_DIGITS = -3;
053    
054        /**
055         * enum for too many digits
056         */
057        static final int TOO_MANY_DIGITS = -2;
058    
059        /**
060         * enum for unknown vendor
061         */
062        static final int UNKNOWN_VENDOR = -1;
063    
064        /**
065         * enum for Visa
066         */
067        static final int VISA = 7;
068    
069        /**
070         * Used to speed up findMatchingRange by caching the last hit.
071         */
072        private static int cachedLastFind = 0;
073    
074        /**
075         * ranges of credit card number that belong to each company. buildRanges
076         * initialises.
077         */
078        private static LCR[] ranges;
079    
080        /**
081         * used by vendorToString to describe the enumerations
082         */
083        private static final String[] vendors =
084                {"Error: not enough digits",
085                 "Error: too many digits",
086                 "Error: unknown credit card company",
087                 "dummy",
088                 "Amex",
089                 "Diners/Carte Blanche",
090                 "Discover",
091                 "enRoute",
092                 "JCB",
093                 "MasterCard",
094                 "Visa"};
095    
096        // -------------------------- STATIC METHODS --------------------------
097    
098        static
099            {
100            // now that all enum constants defined
101            buildRanges();
102            }
103    
104        /**
105         * build table of which ranges of credit card number belong to which vendor
106         */
107        private static void buildRanges()
108            {
109            // careful, no lead zeros allowed
110            // low high len vendor mod-10?
111            ranges =
112                    new LCR[] {new LCR( 4000000000000L,
113                                        4999999999999L,
114                                        13,
115                                        VISA,
116                                        true ),
117                               new LCR( 30000000000000L,
118                                        30599999999999L,
119                                        14,
120                                        DINERS,
121                                        true ),
122                               new LCR( 36000000000000L,
123                                        36999999999999L,
124                                        14,
125                                        DINERS,
126                                        true ),
127                               new LCR( 38000000000000L,
128                                        38999999999999L,
129                                        14,
130                                        DINERS,
131                                        true ),
132                               new LCR( 180000000000000L,
133                                        180099999999999L,
134                                        15,
135                                        JCB,
136                                        true ),
137                               new LCR( 201400000000000L,
138                                        201499999999999L,
139                                        15,
140                                        ENROUTE,
141                                        false ),
142                               new LCR( 213100000000000L,
143                                        213199999999999L,
144                                        15,
145                                        JCB,
146                                        true ),
147                               new LCR( 214900000000000L,
148                                        214999999999999L,
149                                        15,
150                                        ENROUTE,
151                                        false ),
152                               new LCR( 340000000000000L,
153                                        349999999999999L,
154                                        15,
155                                        AMEX,
156                                        true ),
157                               new LCR( 370000000000000L,
158                                        379999999999999L,
159                                        15,
160                                        AMEX,
161                                        true ),
162                               new LCR( 3000000000000000L,
163                                        3999999999999999L,
164                                        16,
165                                        JCB,
166                                        true ),
167                               new LCR( 4000000000000000L,
168                                        4999999999999999L,
169                                        16,
170                                        VISA,
171                                        true ),
172                               new LCR( 5100000000000000L,
173                                        5599999999999999L,
174                                        16,
175                                        MASTERCARD,
176                                        true ),
177                               new LCR( 6011000000000000L,
178                                        6011999999999999L,
179                                        16,
180                                        DISCOVER,
181                                        true )}; // end table initialisation
182            }
183    
184        /**
185         * Finds a matching range in the ranges array for a given creditCardNumber.
186         *
187         * @param creditCardNumber number on card.
188         *
189         * @return index of matching range, or NOT_ENOUGH_DIGITS or UNKNOWN_VENDOR
190         *         on failure.
191         */
192        protected static int findMatchingRange( long creditCardNumber )
193            {
194            if ( creditCardNumber < 1000000000000L )
195                {
196                return NOT_ENOUGH_DIGITS;
197                }
198            if ( creditCardNumber > 9999999999999999L )
199                {
200                return TOO_MANY_DIGITS;
201                }
202            // check the cached index first, where we last found a number.
203            if ( ranges[ cachedLastFind ].low <= creditCardNumber
204                 && creditCardNumber <= ranges[ cachedLastFind ].high )
205                {
206                return cachedLastFind;
207                }
208            for ( int i = 0; i < ranges.length; i++ )
209                {
210                if ( ranges[ i ].low <= creditCardNumber
211                     && creditCardNumber <= ranges[ i ].high )
212                    {
213                    // we have a match
214                    cachedLastFind = i;
215                    return i;
216                    }
217                } // end for
218            return UNKNOWN_VENDOR;
219            } // end findMatchingRange
220    
221        
222        public static boolean isValid(String strCreditCardNumber) {
223            return isValid(toLong(strCreditCardNumber, 0L));
224        }
225    
226            private static long toLong(String strCreditCardNumber, long defaultValue) {
227            
228            if(strCreditCardNumber==null) return defaultValue;
229            
230            // strip commas, spaces, + AND -
231            StringBuffer sb = new StringBuffer( strCreditCardNumber.length() );
232            for (int i = 0; i < strCreditCardNumber.length(); i++ ) {
233                char c = strCreditCardNumber.charAt( i );
234                if(!StringUtil.isWhiteSpace(c) && c!=',' && c!='+' && c!='-')
235                    sb.append( c );
236            }
237            long num=(long) Caster.toDoubleValue(sb.toString(),0L);
238            if(num==0L) return defaultValue;
239            
240            return num;
241        }
242        
243        /**
244         * Determine if the credit card number is valid, i.e. has good prefix and
245         * checkdigit. Does _not_ ask the credit card company if this card has been
246         * issued or is in good standing.
247         *
248         * @param creditCardNumber number on card.
249         *
250         * @return true if card number is good.
251         */
252        public static boolean isValid( long creditCardNumber ) {
253            int i = findMatchingRange( creditCardNumber );
254            if ( i < 0 ) {
255                return false;
256            }
257            //else {
258                // we have a match
259                if ( ranges[ i ].hasCheckDigit )
260                    {
261                    // there is a checkdigit to be validated
262                    /*
263                     * Manual method MOD 10 checkdigit 706-511-227 7 0 6 5 1 1 2 2 7
264                     * 2 * 2 * 2 * 2 --------------------------------- 7 + 0 + 6
265                     * +1+0+ 1 + 2 + 2 + 4 = 23 23 MOD 10 = 3 10 - 3 = 7 -- the
266                     * check digit Note digits of multiplication results must be
267                     * added before sum. Computer Method MOD 10 checkdigit
268                     * 706-511-227 7 0 6 5 1 1 2 2 7 Z Z Z Z
269                     * --------------------------------- 7 + 0 + 6 + 1 + 1 + 2 + 2 +
270                     * 4 + 7 = 30 30 MOD 10 had better = 0
271                     */
272                    long number = creditCardNumber;
273                    int checksum = 0;
274                    // work right to left
275                    for ( int place = 0; place < 16; place++ )
276                        {
277                        int digit = (int) ( number % 10 );
278                        number /= 10;
279                        if ( ( place & 1 ) == 0 )
280                            {
281                            // even position (0-based from right), just add digit
282                            checksum += digit;
283                            }
284                        else
285                            { // odd position (0-based from right), must double
286                            // and add
287                            checksum += z( digit );
288                            }
289                        if ( number == 0 )
290                            {
291                            break;
292                            }
293                        } // end for
294                    // good checksum should be 0 mod 10
295                    return ( checksum % 10 ) == 0;
296                    }
297                return true; // no checksum needed
298                    
299                //} // end if have match
300            } // end isValid
301    
302    
303        
304        /**
305         * Determine the credit card company. Does NOT validate checkdigit.
306         *
307         * @param creditCardNumber number on card.
308         *
309         * @return credit card vendor enumeration constant.
310         */
311        public static int recognizeVendor( long creditCardNumber )
312            {
313            int i = findMatchingRange( creditCardNumber );
314            if ( i < 0 )
315                {
316                return i;
317                }
318            return ranges[ i ].vendor;
319                
320            } // end recognize
321    
322        // From http://www.icverify.com/
323        // Vendor Prefix len checkdigit
324        // MASTERCARD 51-55 16 mod 10
325        // VISA 4 13, 16 mod 10
326        // AMEX 34,37 15 mod 10
327        // Diners Club/
328        // Carte Blanche
329        // 300-305 14
330        // 36 14
331        // 38 14 mod 10
332        // Discover 6011 16 mod 10
333        // enRoute 2014 15
334        // 2149 15 any
335        // JCB 3 16 mod 10
336        // JCB 2131 15
337        // 1800 15 mod 10
338    
339        public static String toCreditcard(String strCreditCardNumber) throws ExpressionException {
340            long number=toLong(strCreditCardNumber, -1);
341            if(number==-1) throw new ExpressionException("invalid creditcard number ["+strCreditCardNumber+"]");
342            return toPrettyString(number);
343        }
344        public static String toCreditcard(String strCreditCardNumber, String defaultValue) {
345            long number=toLong(strCreditCardNumber, -1);
346            if(number==-1) return defaultValue;
347            return toPrettyString(number);
348        }
349        
350        /**
351         * Convert a creditCardNumber as long to a formatted String. Currently it
352         * breaks 16-digit numbers into groups of 4.
353         *
354         * @param creditCardNumber number on card.
355         *
356         * @return String representation of the credit card number.
357         */
358        private static String toPrettyString( long creditCardNumber )
359            {
360            String plain = Long.toString( creditCardNumber );
361            // int i = findMatchingRange(creditCardNumber);
362            int length = plain.length();
363    
364            switch ( length )
365                {
366                case 12 :
367                    // 12 pattern 3-3-3-3
368                    return plain.substring( 0, 3 )
369                           + ' '
370                           + plain.substring( 3, 6 )
371                           + ' '
372                           + plain.substring( 6, 9 )
373                           + ' '
374                           + plain.substring( 9, 12 );
375    
376                case 13 :
377                    // 13 pattern 4-3-3-3
378                    return plain.substring( 0, 4 )
379                           + ' '
380                           + plain.substring( 4, 7 )
381                           + ' '
382                           + plain.substring( 7, 10 )
383                           + ' '
384                           + plain.substring( 10, 13 );
385    
386                case 14 :
387                    // 14 pattern 2-4-4-4
388                    return plain.substring( 0, 2 )
389                           + ' '
390                           + plain.substring( 2, 6 )
391                           + ' '
392                           + plain.substring( 6, 10 )
393                           + ' '
394                           + plain.substring( 10, 14 );
395    
396                case 15 :
397                    // 15 pattern 3-4-4-4
398                    return plain.substring( 0, 3 )
399                           + ' '
400                           + plain.substring( 3, 7 )
401                           + ' '
402                           + plain.substring( 7, 11 )
403                           + ' '
404                           + plain.substring( 11, 15 );
405    
406                case 16 :
407                    // 16 pattern 4-4-4-4
408                    return plain.substring( 0, 4 )
409                           + ' '
410                           + plain.substring( 4, 8 )
411                           + ' '
412                           + plain.substring( 8, 12 )
413                           + ' '
414                           + plain.substring( 12, 16 );
415    
416                case 17 :
417                    // 17 pattern 1-4-4-4-4
418                    return plain.substring( 0, 1 )
419                           + ' '
420                           + plain.substring( 1, 5 )
421                           + ' '
422                           + plain.substring( 5, 9 )
423                           + ' '
424                           + plain.substring( 9, 13 )
425                           + ' '
426                           + plain.substring( 13, 17 );
427    
428                default :
429                    // 0..11, 18+ digits long
430                    // plain
431                    return plain;
432                } // end switch
433            } // end toPrettyString
434    
435        /**
436         * Converts a vendor index enumeration to the equivalent words. It will
437         * trigger an ArrayIndexOutOfBoundsException if you feed it an illegal
438         * value.
439         *
440         * @param vendorEnum e.g. AMEX, UNKNOWN_VENDOR, TOO_MANY_DIGITS
441         *
442         * @return equivalent string in words, e.g. "Amex" "Error: unknown vendor".
443         */
444        public static String vendorToString( int vendorEnum )
445            {
446            return vendors[ vendorEnum - NOT_ENOUGH_DIGITS ];
447            } // end vendorToString
448    
449        /**
450         * used in computing checksums, doubles and adds resulting digits.
451         *
452         * @param digit the digit to be doubled, and digit summed.
453         *
454         * @return // 0->0 1->2 2->4 3->6 4->8 5->1 6->3 7->5 8->7 9->9
455         */
456        private static int z( int digit )   {
457            if ( digit == 0 )       {
458                return 0;
459            }
460            return ( digit * 2 - 1 ) % 9 + 1;
461        }
462    
463        // --------------------------- main() method ---------------------------
464    
465        /**
466         * Test driver
467         *
468         * @param args not used
469         */
470        public static void main( String[] args )
471            {
472            if ( DEBUGGING )
473                {
474                    
475                    
476    
477                System.out.println( isValid( "0" ) ); 
478                System.out.println( isValid( "4000000000006" ) ); 
479                System.out.println( isValid( "40000000-000,06" ) ); 
480                
481                System.out.println( isValid( 0 ) ); 
482                System.out.println( isValid( 6010222233334444L ) ); // false
483                System.out.println( isValid( 4000000000000L ) ); // false
484                System.out.println( isValid( 4000000000006L ) ); // true
485                System.out.println( isValid( 4000000000009L ) ); // false
486                System.out.println( isValid( 4999999999999L ) ); // false
487                System.out.println( isValid( 378888888888858L ) ); // true, Amex
488                System.out.println( isValid( 4888888888888838L ) ); // true, Visa;
489                System.out.println( isValid( 5588888888888838L ) ); // true, MC
490                System.out.println( isValid( 6011222233334444L ) ); // true, Dicover
491    
492                } // end if debugging
493            } // end main
494    } // end class CreditCard
495    
496    /**
497     * Describes a single Legal Card Range
498     */
499    class LCR {
500    
501        // ------------------------------ FIELDS ------------------------------
502    
503        /**
504         * does this range have a MOD-10 checkdigit?
505         */
506        public boolean hasCheckDigit;
507    
508        /**
509         * low and high bounds on range covered by this vendor
510         */
511        public long high;
512    
513        /**
514         * how many digits in this type of number.
515         */
516        public int length;
517    
518        /**
519         * low bounds on range covered by this vendor
520         */
521        public long low;
522    
523        /**
524         * enumeration credit card service
525         */
526        public int vendor;
527    
528        // --------------------------- CONSTRUCTORS ---------------------------
529    
530        /**
531         * public constructor
532         *
533         * @param low           lowest credit card number in range.
534         * @param high          highest credit card number in range
535         * @param length        length of number in digits
536         * @param vendor        enum constant for vendor
537         * @param hasCheckDigit true if uses mod 10 check digit.
538         */
539        public LCR( long low,
540                    long high,
541                    int length,
542                    int vendor,
543                    boolean hasCheckDigit )
544            {
545            this.low = low;
546            this.high = high;
547            this.length = length;
548            this.vendor = vendor;
549            this.hasCheckDigit = hasCheckDigit;
550            } // end public constructor
551    } // end class LCR