Working with FoxyCart data feeds in CFML — sebduggan.uk Skip to main content
sebduggan.uk

Working with FoxyCart data feeds in CFML

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:

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.

{% codetitle "foxychimp.cfm" %}

html
<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

{% codetitle "RC4.cfc" %}

html
<!--- 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>