Ok, please forgive the overly dramatic title, but I want to talk about something pretty darn important and I wanted to get your attention. Does your site have an e-commerce feature? Is the main purpose of your site e-commerce? If so - let me ask you a question. I'm sure you can tell me right now (I hope!) how many orders your site gets per day, along with other things like the average size of the order, highest order, etc. But can you tell me how many people visit your site, add a few items to their cart, and just give up never to return? Can you tell me if there is a particular part of your checkout process where people tend to abandon their carts? If you can't - then you could (literally) be losing money every day. Money isn't everything - but I've heard good things about it - so why not go the extra mile to ensure you are losing money due to some stupid reason like a hard to navigate checkout process or even a simple typo that makes it difficult for consumers to complete their order. Luckily we can add some monitoring using a simple feature of ColdFusion MX7: Application.cfc.
For this blog entry I've created a very simple e-commerce application. It is so simple it doesn't even have real forms. It does, however, have a set of files that let you mimic a user moving through the site and the checkout process. Please download the files by clicking the Download link below. Having them in front of you will help you understand this blog entry. You can expand the zip and run as is - no need for a database or anything fancy like that.
The application has the following files:
- Application.cfc: This is the file that will handle monitoring the check out process. It also initializes the session.
- index.cfm: This is the first page you should hit when testing the application.
- addcart.cfm: This page represents a user who has items in her cart.
- checkout1.cfm: This page represents someone who is on the first step of the checkout process, the address page typically.
- checkout2.cfm: This page represents someone who is on the second step of the checkout process, the billing information page.
- checkout3.cfm: This page represents the confirmation page. The user must go past this page to finish the order.
- done.cfm: This page represents the end of the e-commerce process and a successful order. (Yeah money!)
- stats.cfm: This is a page that would normally be for administrators only. We will use it for reporting.
Feel free to play with the checkout process now, but I'd ask that you avoid the stats page until we discuss it. The application has an extremely low timeout value, so you can leave it for 30 seconds and your session will abort. I don't auto-reset you in the checkout process, so you can just keep clicking away. Now that you've played with it a bit. Let's dig into the code. First let's take a look at the Application.cfc file:
<cfcomponent output="false">
<cfset this.name = "ecomtest">
<cfset this.applicationTimeout = createTimeSpan(0,2,0,0)>
<cfset this.clientManagement = true>
<cfset this.clientStorage = "registry">
<cfset this.loginStorage = "session">
<cfset this.sessionManagement = true>
<cfset this.sessionTimeout = createTimeSpan(0,0,0,30)>
<cfset this.setClientCookies = true>
<cfset this.setDomainCookies = false>
<cfset this.scriptProtect = false>
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfreturn true>
</cffunction>
<cffunction name="onApplicationEnd" returnType="void" output="false">
<cfargument name="applicationScope" required="true">
</cffunction>
<cffunction name="onRequestStart" returnType="boolean" output="false">
<cfargument name="thePage" type="string" required="true">
<cfreturn true>
</cffunction>
<cffunction name="onRequestEnd" returnType="void" output="false">
<cfargument name="thePage" type="string" required="true">
</cffunction>
<cffunction name="onError" returnType="void" output="true">
<cfargument name="exception" required="true">
<cfargument name="eventname" type="string" required="true">
<cfdump var="#arguments#" label="Exception">
</cffunction>
<cffunction name="onSessionStart" returnType="void" output="false">
<cfset session.status = "">
</cffunction>
<cffunction name="onSessionEnd" returnType="void" output="false">
<cfargument name="sessionScope" type="struct" required="true">
<cfargument name="appScope" type="struct" required="false">
<!--- just in case... --->
<cfif not structKeyExists(arguments.sessionScope, "status")>
<cfset arguments.sessionScope.status = "">
</cfif>
<cflog file="ecomtest" text="Session Status: #arguments.sessionScope.status#">
</cffunction>
</cfcomponent>
Let me pick out the parts you want to be concerned with. First off, note the super low session time out:
<cfset this.sessionTimeout = createTimeSpan(0,0,0,30)>
You would obviously not have this on a production system. I did this to make my testing easier. Next lets take a look at the onSessionStart code:
<cffunction name="onSessionStart" returnType="void" output="false">
<cfset session.status = "">
</cffunction>
The session variable, status, is what I'm going to use to note where the user was when her session ended. Speaking of the end of a session...
<cffunction name="onSessionEnd" returnType="void" output="false">
<cfargument name="sessionScope" type="struct" required="true">
<cfargument name="appScope" type="struct" required="false">
<!--- just in case... --->
<cfif not structKeyExists(arguments.sessionScope, "status")>
<cfset arguments.sessionScope.status = "">
</cfif>
<cflog file="ecomtest" text="Session Status: #arguments.sessionScope.status#">
</cffunction>
First - a reminder. When ColdFusion fires the onSessionEnd method, you can't use "session.foo" to refer to the session scope. Why? Because it's dead. There is no more session. However - ColdFusion passes in the data from the session scope as a structure. (It also passed in the Application scope.) I wrote a bit of code to ensure my session variable, status, still existed. It should never fire - but I like to be extra careful. Finally I use the cflog command to report the session status. I could have inserted into a database as well, but I wanted to keep this demo simple. The important thing to note is - I record the value so I know exactly what was going on when the user's session ended.
Now let's look at the checkout process. I described the files above. If you took the time to actually do the checkout process, then you know that each page simply has a text description and a button. Therefore I'm not going to show you all the pages, but just two so you can get the idea. Let's look at the very first page, index.cfm:
<!--- Record Status --->
<cfset session.status = "No Cart">
<cf_layout title="Intro Page">
<p> This page represents the introduction, or home page, for the simulation. If you end your session here, it means you didn't purchase a darn thing. </p>
<p> <form action="addcart.cfm" method="post"> <input type="submit" value="Add Stuff to Your Cart"> </form> </p>
</cf_layout>
There is one line you want to care about - the first line. This is where I set the status for the user's session. I'm using a simple string to describe where she is in the e-commerce process. Let's compare this to addcart.cfm:
<!--- Record Status --->
<cfset session.status = "Cart with Items">
<cf_layout title="Added to Cart">
<p>
You have now added stuff to your cart. If you leave here, it means you never even tried
to check out.
</p>
<p>
<form action="checkout1.cfm" method="post">
<input type="submit" value="Start Checkout Process">
</form>
</p>
</cf_layout>
Again - the only line here of import is the first line. Note how I've changed the status. Now obviously these files would have a lot more to them. Like - oh - actual form fields. But I think you get the idea of what I'm doing here. So - take a look at stats.cfm. If everything is working fine, you should see a report like so:
Your site has had 22 total sessions.
Of those sessions, 10 (45.45 %) sessions didn't order anything.
Of those sessions, 1 (4.55 %) sessions ordered items, but didn't start the checkout process.
Of those sessions, 1 (4.55 %) stopped at the first step in the check out process (Entering Address).
Of those sessions, 2 (9.09 %) stopped at the second step in the check out process (Entering Billing Information).
Of those sessions, 3 (13.64 %) stopped at the third step in the check out process (Confirmation).
Of those sessions, 3 (13.64 %) finished their order.
This report shows exactly where folks are ending in their process. (In case you are curious - the reason why the lines below the total don't add up to the session total represents the fact that some people don't hit the initial page.) For an e-tailer (I really hate all those "e-" words), the lines you would care most about are the lines involved in the process itself. If you saw a spike in step two for example, you would inspect that form and see if anything stands out as being particularly difficult. You could also email a few users and simply ask them why they gave up. (You don't want to email all of them though, just pick a few select random folks.) In my sample code I just logged the status, but you could store a lot more information if you wanted to. Although a log file probably wouldn't be a good idea - you could store their cart - their IP - anything you can think of.
In case you are curious - what exactly is stats.cfm doing? Honestly I don't want to waste space here. Because I used a log file, I had to do some string parsing and query of queries. Since that really isn't relevant to the discussion, I won't post the code - but you can peruse it via the Download link below.
Here are some other ideas to consider:
- Keep a count (in the application scope perhaps) of the people who don't finalize the e-commerce process. If the count goes over a threshold, fire off an email to the powers that be to let them know that something may be up.
- When a user doesn't complete their order, consider storing their cart so they can retrieve it when she returns. I can give you a great example of this. My wife, for the past few days, has been trying to order from a retailer I won't name. As far as she knew - her cart was stored, yet every day she returned to be told her cart had been emptied. This is with a site she had to logon and register for - so there is no reason for this. She was very frustrated and probably will switch to another store.
- When a user doesn't complete their order, maybe send them a coupon code to encourage them to complete an order.
- Examine the contents of abandoned carts. Do some products seems to be abandoned most often? Perhaps they have higher taxes, or shipping fees, that discourage users from actually purchasing the items. Maybe you should be more up front with the users about these fees?
- Can you think of some other good uses here?
Archived Comments
Ray, you are an effing God-send! Your examples and this blog are exactly what Adobe needs to use a business model when it comes to marketing ColdFusion!
I get so tired of seeing and hearing things like, converting ColdFusion to PHP, or converting ColdFusion to Java/JSP, or need someone who knows Ruby on Rails.
The CF community seems to be in this protective circle, like a herd of elephants protecting it's young from the on-slaught of Ajax, php, *insert other language here*, and web 2.whatever!
(Sometimes I think the "intellectual elephants" make it hard to move from middle-user to advanced-user because they talk above everyone else, but anyway...)
The stigma(s) and labels should have been erased years ago. It's been too long and this product is too good and powerful to be just seen as a secondary language by those in the mainstream development community.
So thank you Ray (and thanks to Ben, Matt, Sean, Joe and those others who I may have forgotten - ok, Boyzoid too)
I'm just tired of feeling beaten down and slapped because we use ColdFusion and not being able to find opportunities in our own places in the world where we have the opportunity to use ColdFusion.
Just my 0.02
Ray,
A very valid point. Recording where a user ends is just as important as where a user begins. OnRequestStart would be the next evolution of this idea.
Application.cfc really never ceases to amaze me in how many different ways you can use it. I can understand why popular CF frameworks mimic similar ideas to make things easier.
Thanks as always, ray.
Actually, I wouldn't use onRequestStart. I'd use onSessionStart. If you used onRequestStart, you would need to use logic to only record it once.
this.clientStorage = 'registry'? Please tell me that is a typo.
It came from my App.cfc skeleton, and is the default. :) Sorry.
Ray,
I don't want this to sound like I'm being a dick, but why would you want to do this when Google Analytics is FREE for anyone to use now and has more reports then you could ever dream of. Coupled with the fact that someone else is doing all the monitoring so your server doesn't have to take the hit. What you doing in this article is really cool, on a very very small scale. Try doing this with a site that gets any amount of decent traffic and you'll bring it to it's knees.
I disagree Tony.
1) Google Analytics is great - but is not tuned for reporting of this nature I'd say. I know you can do conversion reporting - but when it comes to something as important as this - I'd want more control.
2) What if you need answers faster than GA can provide? GA is sometimes 24 hours behind when it comes to stats.
3) Bring it to its knees???? How?? We are talking about recording data onSessionEnd. In my example it was simply a string. If you recorded more, like the contents of a cart, then you mean a simple array. I do not see this as a lot of data, and, it was data already in the sesison anyway. At most, I force the data to exist for a MS more before CF finally trashes it. At my last employer, we did this for a large ecommerce site that got a _lot_ of traffic and it didn't add anything at all to the load.
Forget this "coldfusion talk" google analytics is one way, but the money u outlay on coldfusion could be well spent in other places, get php it is free and it is being used in a lot more places, coldfusion is dying, you only have to look at the list of programming languages to see that coldfusion in well down the list even after being around for over ten years - that doesn't bode well
Hi Pragmatic. I bet you are the same person who commented on Ben's blog and lied about me. You didn't have the guts to use your own name there either.
If you feel PHP is better than CF - that is certainly fine. But how can you expect anyone to beleive you if you are too scared to post your name, AND, you lie and say your blog url is Ben Forta's?
PB is indeed correct that CF isn't the most popular programming language. In fact according to this, it's about 29th:
http://www.tiobe.com/tpci.htm
I'm now off to re-learn those up and coming languages Cobol, Prolog and Pascal ;-)
Ray, you have touched on a good point and reminded me of a great article that I just can't find the url for. Basically many of us think of a successful site or application as being an app or site delivered in time or in budget with the requested features, but for the stakeholders a successful site is probably one that generates additional income or converts prospects to actual customers. In order to achieve this we should start thinking about ways to test how successful and usable our application is - so not just examining empty shopping carts but how many people are giving up registering because they can't find a unique user name - (or giving up becuase they have to register) are people always confusing the credit card start date fields with the card expiry date fields?, how many people are doing a free text search for products that we do not stock - are people putting in adsfasdfadsfadsfdsaf@Adsfd.com as their email address? are they concerned about privacy? How many visitors are checking our 'about us page' before purchasing - these are all things we can programmatically check for, examine and use to make improvements to our sites/systems
---------------------------
Just as a pratical example (and not a dig at your blog software) - everytime I post a comment I forget to put in http:// when entering my website granted this is just a blog but you could log how many times people do that and tweak your site accordingly.
Kola