Creating a Grails Execute and Wait Interceptor (a Grails Service)

by norith on February 23, 2009

in Programming

Creating a Grails Execute and Wait Interceptor (a Grails Service) A year or so ago I was building a simple payment processing website using Grails and realized that I wanted an ‘execute and wait’ interceptor’, similar to what I had become used to in WebWork (the basis of Struts2). I wanted the user to be able to submit their payment information, and the page to refresh with progress information and then finally display the results. WebWork’s EaWI prevents the HTTP request from timing out by wrapping the action request in a background thread and returning a result to the browser. The browser can then ‘check in’ periodically, using a META refresh or AJAX call, and retrieve the current status of the backgrounded thread. When the background process has completed, the next browser check in cycle displays the result to the user. While this is simple to implement in WebWork, Grails doesn’t seem to have a similar concept. I ended up using a Grails Service, and checking in on it’s progress periodically. First, handle the form submit:

class OrderController {
// grails injected services
def paymentService
...
def payment = { CreditCard creditcard ->
if (request.method != 'POST') {
// error   flash.message = 'A problem occurred in your order'
redirect(action : 'index')
return
}
def orderDetails = new OrderDetails(params)
if (!creditcard.validate() || !orderDetails.validate()) {
return [ creditcard : creditcard, orderDetails : orderDetails ]
}
if (paymentService.inUse) {
flash.message = "A credit card submission for you already appears to be in progress. Please return again after 30-40 minutes."
return [ creditcard : creditcard, orderDetails : orderDetails ]
}
session.orderDetails = orderDetails
paymentService.processCreditCard(creditcard, orderDetails)
redirect(action : 'processing')
}
...
}</pre>
The action <code>payment</code> begins with defining a <a href="http://grails.org/doc/1.0.x/guide/single.html#6.1.9%20Command%20Objects">Command Object</a> which allows you to bind posted form fields to a temporary object that supports the standard validation system. I’m not using a domain object here because the user’s credit card details should never be persisted on your system. They’re privileged information that you should only hold for the purposes of actually processing a transaction. Next, a simple check to ensure that the user is submitting the form via a ‘POST’ request. Though it’s a simple <a href="http://www.owasp.org/index.php/Cross_Site_Scripting">XSS</a> check, a GET request should never change or store information on the system. The order details are then captured from the form and the two objects validated. After that a quick check to ensure that the <code>paymentService</code> is not already in use by this user. Though there’s a possibility of a race condition here if there are two quick form submissions, it’s still best to check. The <code>orderDetails</code> are stored in the session for later use and the <code>paymentService</code>is then called to process the transaction and the browser shown a processing status page.
<pre class="brush:groovy">def declined = {
flash.message = "Your payment was declined " + flash.errorMsg
if (!session.orderDetails)
render(view : 'payment')
else
render(view : 'payment', model : [ orderDetails : session.orderDetails ])
}
def processing = {
if (!paymentService.inUse) {
flash.message = "A problem occurred processing your order. "
if (!session.orderDetails)
render(view : 'payment')
else
render(view : 'payment', model : [ orderDetails : session.orderDetails ])
return
}
// show the 'your order is being processed' waiting page
if (paymentService.finishedProcessing) {
def orderDetails = paymentService.processedOrderDetails
paymentService.processedOrderDetails = null
if (paymentService.hasError || paymentService.declined) {
flash.errorMsg = paymentService.getErrorMsg()
paymentService.inUse = false
redirect(action : declined)
return
}
paymentService.inUse = false
flash.orderDetails = orderDetails
redirect(action : 'finished')
return
}
[orderDetails : orderDetails]
}

The declined action simply displays the error message to the user and likely provides them with a way to change their payment information. The processing action gets called repeatedly to check in on the status of the credit card transaction. It first checks to make sure that are actually in the middle of processing a card, then checks whether the transaction is completed. If it is, then the orderDetails are retrieved from the paymentService and if the transaction was declined the user is sent to the declined action. If the transaction was approved we’re redirected to the finishedaction. Otherwise we’re shown the in-progress status page.

def finished = {
if (!flash.orderDetails) {
flash.message = "A problem occurred processing your order. "
if (!session.orderDetails)
render(view : 'payment')
else
render(view : 'payment', model : [ orderDetails : session.orderDetails ])
return
}
session.removeAttribute('orderDetails')
def orderDetails = flash.orderDetails
[orderDetails : orderDetails]
}

The finished action displays the Thank You page to the user with the details of the order. But the interesting bit of the code is the paymentService

class PaymentService {
static scope = 'session'
static transactional = true
boolean inUse = false
boolean finishedProcessing = false
boolean hasError = false
boolean declined = false
OrderDetails processedOrderDetails
synchronized processCreditCard(CreditCard cc, OrderDetails orderDetails) {
while (inUse) {
// already processing a credit card from another submission
sleep 500
}
inUse = true
finishedProcessing = false
hasError = false
declined = false
// store the order in the DB before we go to the payment processor
// this ensures that if our server goes down or
// we don't hear back from the processor, but the card is authorized
// we have a record of the purchase. Also it builds an incrementing
// unique orderid which is usually required by the payment processor
OrderCache orderCache = new OrderCache()
orderCache.cache = orderDetails.dump()
orderCache.save()
String orderId = 'CC-' + orderCache.id.toString()
processedOrder = orderDetails
// create the transaction request
PreAuth preAuthRequest = new PreAuth(
orderId,
cc.number,
cc.expiryMonth,
cc.expiryYear,
cc.name,
cc.cvv,
...
);
// create a thread to run the payment request
Thread.start {
hasError = false
try {
// the constructor actually performs the remote call to the processor
// so it will possibly take real time
HttpsPostRequest paymentReq =
new HttpsPostRequest(
preAuthRequest
...
);
processedOrder.receipt = ...
if (paymentReq.isSuccessful) {
// save the order to the database
processedOrder.orderId = orderId
processedOrder.save()
// send confirmation email...
}
else {
declined = true
}
}
catch(Exception e) {
log.error(e.getMessage(), e)
hasError = true
}
finishedProcessing = true
}
}
}

Here we define the PaymentService as being both transactional and having a session scope. This means that it participates in transactional database scopes and that each browser session will be guaranteed a unique instance of the PaymentService. For safety’s sake, I first cached the order details to ensure that if the unthinkable happens and the server computer fails during processing we’ll still have a record of it, and secondarily it we get a incremented order ID that we can send to the card processor. We build the payment request, and start a thread so that the actual processing will happen in the background. Flow of control then returns back to the controller so that we can send a status update to the user. In the mean time the thread is waiting for the payment request to return and if it turns out to be successful it saves the order to the database and updates a flag indicating that we’re finished. At this point the next time the user’s browser comes back to check the order status, the controller will notice that it’s finished and display the appropriate result. While this code is a bit abstracted, the major components are there. One rough point would be how Grails session objects are handled between requests (or across servers if they’re replicated across a cluster). Since the service object is unique to the session it’s possible that it might be serialized too, Tomcat will do this just between normal shutdowns and restarts. That would be an interesting experiment.

Comments on this entry are closed.

Previous post:

Next post: