Web Software Architecture and Engineering – Life on the Bleeding Edge

So we’re finally doing e-Commerce. I needed a basic CFC to help check a cc number to see if its valid, give me what cc type it is, and maybe validate the security code as well (depending on its type). Simple yes? Well, I couldn’t find anything like that on the web!
I kept scratching my head, thinking, “That’s not right!” So I put together a CFC with those 3 functions (or one that returns all 3).
Is this useful? Am I re-inventing the wheel (is there some one else’s code I should use)? Before I put this up on RiaForge, I’d love your comments, suggestions, etc.
See below.

<cfcomponent displayname="CreditCardHelper" output="false">

	<!---
	Author: Sami Hoda
	Email: sami <at> bytestopshere.com
	Blog: http://www.bytestopshere.com
	Version: v0.1
	Note: Comments appreciated!

	Warning:
	This code is provided as is.
	I make no warranty or guarantee.
	Use of this code is at your own risk.

	To Do:
	- Expiration Date Check (Must be in future, valid date format)
	- Additional CC Types

	Version

	License:
	Copyright 2008 Sami Hoda

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	    http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
	--->

	<cffunction name="init" access="public" description="" output="false" returntype="CreditCardHelper">
		<cfreturn this />
	</cffunction>

	<cffunction name="getCCInfo" access="public" description="" output="false" returntype="Any">
		<!--- This function will return a struct with information on:
		CC Number Validity,
		CC Type,
		CC Security Code Validity (if provided) --->

		<cfargument name="ccNumber" type="Any" required="true" default="">
		<cfargument name="code" type="Any" required="false" default="">

		<cfset var local = StructNew()/>
		<cfset var returnStruct = StructNew()/>

		<cfset local.ccNumber = _numbersOnly(trim(arguments.ccNumber)) />
		<cfset local.code = _numbersOnly(trim(arguments.code)) />

		<cfset returnStruct.isValidCCNumber = isValidCCNumber(local.ccNumber)/>
		<cfset returnStruct.getCCType = getCCType(local.ccNumber)/>

		<cfif len(local.code)>
			<cfset returnStruct.isValidVerificationNumber = isValidVerificationNumber(local.ccNumber,local.code)/>
		</cfif>

		<cfreturn returnStruct />

	</cffunction>

	<cffunction name="isValidCCNumber" access="public" description="Checks if a CC Number is valid" output="false" returntype="Any">
		<cfargument name="ccNumber" type="Any" required="true" default="">

		<cfset var local = StructNew()/>
		<cfset local.ccNumber = _numbersOnly(trim(arguments.ccNumber)) />

		<!--- Fail By Default --->
		<cfset local.pass = false />

		<cfif _isMod10(local.ccNumber)>
			<cfset local.pass = true />
		</cfif>

		<cfreturn local.pass />
	</cffunction>



	<cffunction name="isValidVerificationNumber" access="public" description="Checks if Verification Number is valid" output="false" returntype="Any">
		<!---
		http://en.wikipedia.org/wiki/Card_Security_Code

		The Card Verification Number/Code anti-fraud system was created so that if someone gets possession of your credit card numbers
		they also need to have possession of your physical card in order to use the card.
		American Express calls it the CID (Card Identification Number).
		For MasterCard, it is the CVC2 (Card Validation Code).
		Visa calls this number the CVV2 (Card Verification Value).

		With MasterCard, Visa or Discover the verification number is a 3-digit number printed on the back of your card.
		It appears after and to the right of your card number.

		The American Express verification number is a 4-digit number printed on the front of your card.
		It appears after and to the right of your card number.

		--->

		<cfargument name="ccNumber" type="Any" required="true" default="">
		<cfargument name="code" type="Any" required="true" default="">

		<cfset var local = StructNew()/>
		<cfset local.ccNumber = _numbersOnly(trim(arguments.ccNumber)) />
		<cfset local.code = _numbersOnly(trim(arguments.code)) />

		<!--- Fail By Default --->
		<cfset local.message = "Code is invalid" />

		<!--- Note: Required only for Amex, Visa, MC, and Discover --->
		<cfswitch expression="#getCCType(local.ccNumber)#">

			<cfcase value="AMERICAN_EXPRESS">
				<cfif isNumeric(local.code) AND len(local.code) EQ 4>
					<cfset local.message = "Code Is Valid" />
				</cfif>
			</cfcase>

			<cfcase value="MASTERCARD">
				<cfif isNumeric(local.code) AND len(local.code) EQ 3>
					<cfset local.message = "Code Is Valid" />
				</cfif>
			</cfcase>

			<cfcase value="VISA">
				<cfif isNumeric(local.code) AND len(local.code) EQ 3>
					<cfset local.message = "Code Is Valid" />
				</cfif>
			</cfcase>

			<cfcase value="DISCOVER">
				<cfif isNumeric(local.code) AND len(local.code) EQ 3>
					<cfset local.message = "Code Is Valid" />
				</cfif>
			</cfcase>

			<cfdefaultcase>
				<cfset local.message = "Code Is Not Required" />
			</cfdefaultcase>

		</cfswitch>


		<cfreturn local.message />
	</cffunction>

	<cffunction name="getCCType" access="public" description="Checks the CC Number Type" output="false" returntype="Any">
		<!---
		The code below may NOT be up to date.
		See:
		http://en.wikipedia.org/wiki/Credit_card_number
		http://www.beachnet.com/~hstiles/cardtype.html
		Etc
		 --->

		<cfargument name="ccNumber" type="Any" required="true" default="">


		<cfset var local = StructNew()/>

		<cfset local.ccNumber = _numbersOnly(trim(arguments.ccNumber)) />

		<!--- Unknown By Default --->
		<cfset local.type = "Unknown" />

		<!--- Can override here if you don't want certain types. Set type to Unknown to override. --->

		<!--- AMERICAN_EXPRESS / Starts with: 34, 37 / Length: 15 --->
		<cfif (left(local.ccNumber, 2) EQ 34 OR left(local.ccNumber, 2) EQ 37) AND len(local.ccNumber) EQ 15>
			<cfset local.type = "AMERICAN_EXPRESS" />

		<!--- MASTERCARD / Starts with: Inclusive Between 51 to 55 / Length: 16 --->
		<cfelseif (left(local.ccNumber, 2) LTE 55 AND left(local.ccNumber, 2) GTE 51) AND len(local.ccNumber) EQ 16>
			<cfset local.type = "MASTERCARD" />

		<!--- VISA / Starts with: 4 / Length: 13, 16 --->
		<cfelseif left(local.ccNumber, 1) EQ 4 AND (len(local.ccNumber) EQ 13 OR len(local.ccNumber) EQ 16)>
			<cfset local.type = "VISA" />

		<!--- DISCOVER / Starts with: 6011, 65 / Length: 16 --->
		<cfelseif (left(local.ccNumber, 4) EQ 6011 OR left(local.ccNumber, 2) EQ 65) AND len(local.ccNumber) EQ 16>
			<cfset local.type = "DISCOVER" />

		<!--- Need to Add More - Diners Club, JCB, etc --->

		</cfif>

		<cfreturn local.type />
	</cffunction>




	<!--- Private Functions --->

	<cffunction name="_numbersOnly" access="private" returntype="any">
		<cfreturn reReplace(arguments[1], "[^[:digit:]]", "", "ALL") />
	</cffunction>

	<cfscript>
	//http://cflib.org/udf/isMod10
	/**
	* Checks to see whether a string passed to it passes the Luhn algorithm (also known as the Mod10 algorithm)
	* V2 update by Christopher Jordan cjordan@placs.net
	* V3 update by Peter J. Farrell (cjordan@placs.netpjf@maestropublishing.com)
	*
	* @param number      String to check. (Required)
	* @return Returns a boolean.
	* @author Scott Glassbrook (cjordan@placs.netpjf@maestropublishing.comcflib@vox.phydiux.com)
	* @version 3, March 2, 2006
	*/
	function _isMod10(number) {
	var nDigits = Len(arguments.number);
	var parity = nDigits MOD 2;
	var digit = "";
	var sum = 0;

	for (i=0; i LTE nDigits - 1; i=i+1) {
	digit = Mid(arguments.number, i+1, 1);
	if ((i MOD 2) EQ parity) {
	digit = digit * 2;
	if (digit GT 9) {
	digit = digit - 9;
	}
	}
	sum = sum + digit;
	}

	if (NOT sum MOD 10) return TRUE;
	else return FALSE;
	}
	</cfscript>


</cfcomponent>

 

Advertisements

Comments on: "CreditCardHelper.cfc – Anyone need this or find it useful?" (5)

  1. ColdFusion’s isValid() function accepts “creditcard” as the type argument; no need for your isMod10(). I know it’s supported in CF7+, but not sure about earlier versions

  2. Jordan Clark said:

    I found it helpful, thanks. I was just about to update our credit card input to add support for CVC/CVV input and automatic recognition of the credit card type.

    Just my $0.02, but in my experience I’ve found making a separate “local” scope in each udf is actually much less efficient then just VAR scoping the individual variables or even setting them into “argument” scope, like in getCCInfo where you basically return the same variables you pass back.

    Thanks again
    Thanks!

  3. Jordan Clark Says: “…in my experience I’ve found making a separate “local” scope in each udf is actually much less efficient then just VAR scoping the individual variables…”

    Can you elaborate on this? How is it less efficient?

  4. Jordan Clark said:

    It runs slower, put the code into a loop that runs a 1000 times and you’ll see it takes longer to execute then the same code that uses all VAR scoped variables.

    I believe this is because every time you execute the UDF you have to create the struct, and every variable access to “local” requires a lookup into it’s hashmap, instead of just directly accessing the VAR variable.

    Let me know if you’ve had different results.

    Cheers

  5. @Eric,

    I wanted my code to be 6.1+ compatible.

    @Jordan,

    The local scope is great for dumping/debugging. If you are running the CFC 1000 times, then feel free to change the variables, otherwise readability and debugging capability trumps speed for me.

    Sami

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: