One of the latest problems we had to solve while working for our full color printing and direct mail marketing company, imageMEDIA, was integration of the LinkShare affiliate program with our custom built ColdFusion e-commerce site.
Here's how we quickly and dirtily solved it.
Overview of integration steps
As per LinkShare documentation, there are 2 major steps that we needed to implement to make this program work:
- Entry (gateway) - Receiving affiliate traffic
- Checkout - Reporting transactions back
Simply put, the people who become your affiliate on LinkShare will use links on their sites that your marketing department sets up. These links might be to a promotion, a special product, and so on. When a potential client clicks an affiliate link, the LinkShare system will process that request and redirect that client to a gateway page on your website. This gateway page receives some parameters (as URL query strings), processes those parameters and redirects the client to the appropriate page. The client will then proceed to order something, and upon checkout, we will report back to LinkShare that the client has placed an order. Your company makes a sale, your affiliate gets credit for that order. We all win!?
Being the lazy developers that we are, we turned to our friend, Google, but we couldn't dig up anything immediatelly relevant to this, particularly not for ColdFusion. So, we had to do this on our own. This would become the reason for this blog post.
Links from publishers in the LinkShare network will direct users to this page and place a (siteID=) value in the URL query string. (siteID=) value varies per click.
The gateway page performs two functions.
1. Set a cookie with the (siteID=) value along with current date/time in GMT format.
2. Redirect users to a URL string, which LinkShare passes to the gateway page as (url=). If no(url=) value is passed by LinkShare, users should be redirected to the site's homepage.
When a potential customer is directed to you by LinkShare, the URL string will look like these examples:
linkshare_gateway.cfm is the gateway page that will process incoming affiliate-referred traffic.
The location of this page is up to you, it is shown here at the root level for simplicity's sake.
Handle incoming affiliate-referred traffic.
<cfparam name="URL.siteID" type="string" default="">
<cfparam name="URL.url" type="string" default="/index.cfm">
<!--- redirect to homepage if siteID is not set --->
<cfif not structKeyExists(URL,"siteID") or URL.siteID eq ''>
<cflocation url="/index.cfm" addtoken="false" />
// Gets GMT date/time from local time
//set how long the cookie will persist on client machine
cookieExpiration = dateAdd('yyyy',1,now());
<!--- check if client already has Linkshare cookie, if so, expire immediatelly --->
<cfcookie name = "LINKSHARE_SITEID" expires = "NOW">
<cfcookie name = "LINKSHARE_GMT" expires = "NOW">
<!--- set cookie --->
<cfcookie name = "LINKSHARE_SITEID" expires = "#cookieExpiration#" value = "#URL.siteID#">
<cfcookie name = "LINKSHARE_GMT" expires = "#cookieExpiration#" value = "#GMT#">
<!--- redirect to relevant URL --->
<cflocation url="#urldecode(URL.url)#" addtoken="false" />
<cfcatch type="any"><cfthrow message="Error processing referrer link" /></cfcatch>
The code above is pretty straightforward, we simply set a cookie on the client's machine, and wait for the client to reach the confirmation page. As long as this cookie is set on the client's machine, a Linkshare transaction can be generated upon order placement. GMT time could be a stickler, but thanks to ColdFusion, pretty trivial to do.
Now that the client has entered your site, and they have been tagged to have come from a LinkShare affiliate, we are ready to wait (and hope) for them to place an order. Since the cookie can be set to expire after a long period, the client can return to your site directly, and, for as long as the cookie exists on their machine, their orders will be reported back to LinkShare and credit will be given to your affiliate.
After the client has completed the checkout, an e-commerce site usually provides a confirmation page. Based on the requirements of your checkout system and confirmation page, you may find a better location for the LinkShare transaction code, we chose to put it on the order confirmation processing template, along with other tracking info, preset messaging, session cleaning functions. This page was for us the best location to ensure each order placed was properly tracked and logged. You may find that this does not work for you, but like any other commerce tracking snippet, the LinkShare transaction, you will quickly identify where to place this code.
The purpose of this code is to accurately report any affiliate transactions back to LinkShare, so that proper credit is given to your affiliates. The code should check for the existence of a LinkShare cookie, construct an XML message with order data and affiliate tracking information, encrypt and URL encode that data and transmit it to the LinkShare web service. The web service will process that data and provide a response. The message format, encryption requirements and response formats are provided in the documentation. In a nutshell, you need to generate an HMACMD5 encrypted, BASE64 encoded, URL safe signature of the transaction XML and send it off.
We chose to also implement a logging mechanism for each transaction we sent to LinkShare in order to be able to verify the integrity of our transactions and for accounting purposes. A quick example call is included in the comments of the code below.
To properly encode, encrypt and transmit the transactional data to LinkShare, you will need the following:
- Site ID - a number provided by LinkShare (ours was 5 digits), like a merchant account number
- Key - an encryption key linked with your LinkShare account for encrypting the transaction data
- Web Service URL - the URL to the Web Service that will receive your transaction and provide transaction results
I had never heard of MD5 HMAC before. Initially, though, looking at the documentation and analyzing the requirements for this implementation, we did not think we would have any issues encrypting the XML. A quick brush-up of the cryptography algorithms included with ColdFusion (awesome!) revealed that hmacmd5 is included with the Enterprise license of CF8.
In our early tests, ColdFusion's included HMACMD5 encryption algorithm did not work for us, and trying to find out why turned out to be a lot of work, so we quickly implemented a java based UDF (actually a function inside one of our Util CFC's) to use the Java cryptography classes. See code further down. If anyone knows of a solution, let us know!
<!--- Check for cookie --->
<cfif StructKeyExists(cookie,"linkshare_siteID") and cookie.linkshare_siteID neq ''>
<!--- get GMT time --->
<!--- prepare transaction data XML string --->
<cfset linkshareTransactionData='<message xmlns="http://www.linkshare.com/namespaces/realtimetransactions-1.0">
<orderid>YOUR ORDER ID</orderid>
<amount>YOUR CART TOTAL</amount>
<product_name>YOUR PRODUCT NAME</product_name>
<!--- Convert to Base64 and URL Encode the XML --->
<cfset xml64 = URLEncodedFormat(toBase64(toString(linkshareTransactionData))) />
<!--- MD5 MAC encode the XML transaction
!!! SEE HMACMD5 code snippet !!!
This functionality was included in a Utils CFC on our system. You may use a UDF version.
<cfset xmlmd5 = createObject("component","/components.utils").hmacmd5(message:linksharetransactiondata,key:'YOUR_KEY',outputFormat:'base64')>
<!--- send transaction to LinkShare --->
<!--- !!! REPLACE the YOUR_SITE_ID string with your own !!! --->
<cfhttp method="get" url="http://track.linksynergy.com/xml?mid=YOUR_SITE_ID&msg=#xml64#&md5=#xmlmd5#&xml=1" result="linkshare_server_response"></cfhttp>
<cfset lsResponse = ''>
<cfif isDefined("linkshare_server_response") and structKeyExists(linkshare_server_response,"filecontent")>
<cfset responseXML = xmlparse(linkshare_server_response.filecontent)>
<cfset lsResponse = '#responseXML.response.error.xmlText#'>
<cfset lsResponse = 'ID: #responseXML.response.unique_id.xmlText# | (Good: #responseXML.response.summary.transactions.good.xmlText# | Bad: #responseXML.response.summary.transactions.bad.xmlText#) | Amount: '>
<cfset lsResponse = 'Unknown data'>
<cfset lsResponse = 'Invalid server response (not XML)'>
<cfset lsResponse = 'No server response'>
<!--- This is just an example of us logging the transaction in our databases, you may write your own
<cfset loglinkshare = createObject("component","/components.linkshareUtils").logOrder(orderid:session.order.orderid,customerid:session.order.customerid,siteid:cookie.linkshare_siteID,response:lsResponse,transXML:linkshareTransactionData,md5Hash:xmlmd5)>
<cfcatch type="any"><cfthrow message="Error processing Linkshare transaction" /></cfcatch>
The real challenge here was ensuring we are properly encrypting the XML transaction data, and that our LinkShare account was reflecting the proper transactions. LinkShare documentation provided fairly clear instructions on what exactly the requirements were, with samples of encrypted and encoded strings, as well as valid response XML.
See the related post for the HMACMD5 encryption function.
We have successfully used this solution for some months now, and I hope you will find it useful as well, even if as a starting point.