On a couple of the sites I manage, I have employed FoxyCart to handle the shopping cart and e-commerce. It provides a simple integration into an existing site with very little fuss, and makes it easy to offload the business of running a webshop.

One of the features offered is to post back an XML data feed of each completed transaction, in real time, to your server. You may want this to maintain your own database of orders placed; in my case, it is used to handle MailChimp subscriptions if they ticked the appropriate box during checkout.

However, the encoding/encrypting of the data feed is somewhat arcane, and has taken me a couple of days, on and off, to get up and running in CFML. In fact, due to time constraints, I initially had to spin up a DigitalOcean droplet running PHP in order to handle the process until I could get it working in CFML. (There was a tried and tested PHP solution which I could get running in no time.)

This guide is to help any other poor souls in the future who have to wrestle with this…

The way it works is this: FoxyCart does an RC4 encryption of the XML feed using your own API key (they’ll generate one for you, or you can set it yourself), and it then URL-encodes the rseult and POSTs it to your server (you’ve previously told them what script on your server to fire it at).

Easy, I thought. urlDecode() and then decrypt() using the RC4 algorithm.

Nope.

After much trial and error (mostly error), and hacking the PHP so I could compare exactly what the data was at each stage of the process, I finally solved it. There are two things you need to bear in mind:

  • When doing the urlDecode(), you need to specify the encoding as ISO-8859-1 (Latin-1). If you use any other encoding, you’ll get a bunch of gibberish with loads of high-ASCII values.
  • Do not use the CFML decrypt() function - it doesn’t seem to work as expected. I found a number of RC4 libraries in various languages, each doing pretty much the same thing, and there was a CFML version written by Steve Hicks. It needed a slight modification, though, as Steve’s version was expecting the encrypted text to be in Hex format - which it isn’t.

Having done that, you should have a nice XML string which you can parse as normal and then do whatever you will with the data.

These are the two files you’ll need to implement the data feed processing - foxychimp.cfm, which is the file you should point at in the FoxyCart admin, and RC4.cfc, which is my modified version of Steve Hicks’ original library.

foxychimp.cfm

<cfset fcApiKey = "Insert_your_FoxyCart_API_key_here" />


<!---
	Decode the URL-encoded response, using the ISO-8859-1
	(Latin-1) encoding
--->
<cfset encryptedFeed = urlDecode(form["FoxyData"], "ISO-8859-1") />


<!---
	Initialise the RC4 component and call its decrypt method.
	Note the 3rd argument, specifying that the data we are
	passing in is not in Hex format
--->
<cfset RC4 = createObject("component","RC4") />
<cfset feedDataXml = RC4.rc4decrypt(src=encryptedFeed, key=fcApiKey, inputHex=false) />


<!---
	Now you have an XML string which you can parse
--->
<cfset feedData = xmlParse(feedDataXml) />


<!---
	...and do what you like with the feed data
--->
<cfloop array="#feedData.foxyData.transactions.transaction#" index="transaction">
	
	<!---
		Do whatever you like with each transaction data in here
	--->
	
</cfloop>


<!---
	FoxyCart expects a simple text response of "foxy"
	to signify that everything has worked.
	
	You can put error handling in which can return any other string,
	which will then show up in your FoxyCart error logs.
--->
<cfcontent reset="true" />
foxy

RC4.cfc

<!---
ColdFusion RC4 Component

Written by Steve Hicks (steve@aquafusionmedia.com)
http://www.aquafusionmedia.com

Version 1.0 - Released: April 24, 2012

Version 1.1 - Modified by Seb Duggan (seb@sebduggan.com)
			  Added arguments to allow input and output not to be Hex values
			  Released: May 3, 2016
--->

<cfcomponent output="false">
	<!--- Encrypt a String (src) using the Key (key) --->
	<cffunction name="RC4encrypt" access="public" output="false" returntype="string">
		<cfargument name="src" required="yes">
		<cfargument name="key" required="yes">
		<cfargument name="outputHex" type="boolean" default="true">
		<cfset mtxt = strToChars(arguments.src)>
		<cfset mkey = strToChars(arguments.key)>
		<cfset result = RC4calculate(mtxt,mkey)>
		<cfif arguments.outputHex>
			<cfreturn charsToHex(result)>
		<cfelse>
			<cfreturn charsToStr(result)>
		</cfif>
	</cffunction>
	
	<!--- Decrypt a String (src) using the Key (key) --->
	<cffunction name="RC4decrypt" access="public" output="false" returntype="string">
		<cfargument name="src" required="yes">
		<cfargument name="key" required="yes">
		<cfargument name="inputHex" type="boolean" default="true">
		<cfif arguments.inputHex>
			<cfset mtxt = hexToChars(arguments.src)>
		<cfelse>
			<cfset mtxt = strToChars(arguments.src)>
		</cfif>
		<cfset mkey = strToChars(arguments.key)>
		<cfset result = RC4calculate(mtxt,mkey)>
		<cfreturn charsToStr(result)>
	</cffunction>

	<!--- Set Up the Component Ready for Encryption --->
	<cffunction name="RC4initialize" access="public" output="false" returntype="any">
		<cfargument name="pwd" required="yes">
		<cfset sbox = arraynew(1)>
		<cfset mykey = arraynew(1)>
		<cfset b = 0>
		<cfset intLength = arraylen(arguments.pwd)>
		<cfloop from="0" to="255" index="a">
			<cfset mykey[a + 1] = arguments.pwd[(a mod intLength) + 1]>
			<cfset sbox[a + 1] = a>
		</cfloop>
		<cfloop from="0" to="255" index="a">
			<cfset b = ( b + sbox[a + 1] + mykey[a+1] ) mod 256>
			<cfset tempswap = sbox[a + 1]>
			<cfset sbox[a + 1] = sbox[b + 1]>
			<cfset sbox[b + 1] = tempswap>
		</cfloop>	
		<cfreturn sbox>
	</cffunction>
	
	<!--- Calculate the Cipher --->
	<cffunction name="RC4calculate" access="public" output="false" returntype="any">
		<cfargument name="plaintext" required="yes">
		<cfargument name="psw" required="yes">
		<cfset sbox = RC4initialize(arguments.psw)>
		<cfset i = 0>
		<cfset j = 0>
		<cfset cipher = arraynew(1)>
		<cfloop from="1" to="#arraylen(plaintext)#" index="a">
			<cfset i = (i + 1) mod 256>
			<cfset j = (j + sbox[i + 1]) mod 256>
			<cfset temp = sbox[i + 1]>
			<cfset sbox[i + 1] = sbox[j + 1]>
			<cfset sbox[j + 1] = temp>
			<cfset k = sbox[((sbox[i + 1] + sbox[j + 1]) mod 256) + 1]>
			<cfset cipherby = BitXor(arguments.plaintext[a],k)>
			<cfset arrayappend(cipher,cipherby)>
		</cfloop>
		<cfreturn cipher>
	</cffunction>

	<!--- Convert an Array of Chars into a Hex String --->
	<cffunction name="charsToHex" access="public" output="false" returntype="string">
		<cfargument name="chars" type="array" required="yes">
		<cfset result = ''>
		<cfloop from="1" to="#arraylen(arguments.chars)#" index="i">
			<cfset fbn = formatBaseN(chars[i],16)>
			<cfif len(fbn) eq 1>
				<cfset fbn = '0#fbn#'>
			</cfif>
			<cfset result = result & fbn>
		</cfloop>
		<cfreturn result>
	</cffunction>
	
	<!--- Convert a Hex String into an Array of Characters --->
	<cffunction name="hexToChars" access="public" output="false" returntype="array">
		<cfargument name="hex" type="string" required="yes">
		<cfset chars = arraynew(1)>
		<cfloop from="1" to="#len(arguments.hex)#" index="i" step="2">
			<cfset arrayappend(chars,inputBaseN(mid(arguments.hex,i,2),16))>
		</cfloop>
		<cfreturn chars>
	</cffunction>
	
	<!--- Convert an Array of Characters into a String --->
	<cffunction name="charsToStr" access="public" output="false" returntype="string">
		<cfargument name="chars" type="array" required="yes">
		<cfset result = ''>
		<cfloop from="1" to="#arraylen(arguments.chars)#" index="i">
			<cfset result = result & chr(chars[i])>
		</cfloop>
		<cfreturn result>
	</cffunction>
	
	<!--- Convert a String into an Array of Characters --->
	<cffunction name="strToChars" access="public" output="false" returntype="array">
		<cfargument name="str" type="string" required="yes">
		<cfset codes = arraynew(1)>
		<cfloop from="1" to="#len(arguments.str)#" index="i">
			<cfset codes[i] = asc(mid(arguments.str,i,1))>
		</cfloop>		
		<cfreturn codes>
	</cffunction>
</cfcomponent>