Monday, May 9, 2011

ISO 8583 with Java and jPOS

I've been busy working on a project that uses ISO 8583. It's a standard for systems that exchange electronic transactions made by cardholders using payment cards. We are using a Java-based library (jPOS) to do take care of un/parsing ISO messages (i.e. requests/responses). As I've worked on some code, it was evident that I could use some helper methods to format commonly used types like date, time, and monetary amounts.

An ISO 8583 message is composed of data elements. Time data elements are formatted as HHmmss (hours-minutes-seconds) strings. Date data elements are formatted as MMdd (month-day) strings. The current code base had a lot of duplicated code that used SimpleDateFormat instances to set the date/time data elements. To remove duplication (and avoid future duplication), we needed a helper method that would format date/time objects correctly and set the desired data element.

We didn't want to extend the ISO 8583 message (ISOMsg) class of jPOS. Instead, we created a helper class that wraps the ISOMsg class, and provides methods that we needed.

Again, we start with a test.

import static org.junit.Assert.*;

import java.util.*;
import org.junit.*;

import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;

public class ISOMsgHelperTest {
private ISOMsg isoMsg = new ISOMsg();
private ISOMsgHelper helper = new ISOMsgHelper(isoMsg);
private Date dateTime =
new GregorianCalendar(2011, Calendar.APRIL, 15, 9, 30, 59).getTime();

@Test
public void setsIsoDateAsMonthDay() throws Exception {
helper.setIsoDate(12, dateTime);
assertEquals("0415", isoMsg.getString(12));
}

@Test
public void setsIsoTimeAsHourMinuteSecond() throws Exception {
helper.setIsoDate(13, dateTime);
assertEquals("093059", isoMsg.getString(13));
}

@Test
public void setsIsoDateTime() throws Exception {
helper.setIsoDate(7, dateTime);
assertEquals("0415093059", isoMsg.getString(7));
}
}


And here's the class that passes the test.


import java.util.Date;
import java.text.*;

import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;

public class ISOMsgHelper {
private ISOMsg isoMsg;
public ISOMsgHelper(ISOMsg isoMsg) {
if (isoMsg == null) {
throw new IllegalArgumentException("isoMsg cannot be null");
}
this.isoMsg = isoMsg;
}
public void setIsoDate(int fldNo, Date date) throws ISOException {
this.isoMsg.set(fldNo, dateFormat.get().format(date));
}
public void setIsoTime(int fldNo, Date time) throws ISOException {
this.isoMsg.set(fldNo, timeFormat.get().format(time));
}
public void setIsoDateTime(int fldNo, Date dateTime) throws ISOException {
this.isoMsg.set(fldNo, dateTimeFormat.get().format(dateTime));
}

public Date getIsoDate(int fldNo) throws ISOException {
if (this.isoMsg.hasField(fldNo)) {
try {
return dateFormat.get().parse(this.isoMsg.getString(fldNo));
} catch (ParseException e) {
throw new ISOException("Error getting date field " + fldNo, e);
}
}
return null;
}
// Other getters were removed to improve clarity

// Need to make SimpleDateFormat objects thread-safe
private static final ThreadLocal<SimpleDateFormat> dateFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MMdd");
}
};

private static final ThreadLocal<SimpleDateFormat> timeFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("HHmmss");
}
};

private static final ThreadLocal<SimpleDateFormat> dateTimeFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MMddHHmmss");
}
};

}


Now, we can easily set date/time fields! And the date formatting objects are nicely encapsulated in one place.

ISOMsg isoMsg = ...;
ISOMsgHelper isoMsgHelper = new ISOMsgHelper(isoMsg);
Date now = new Date();
isoMsgHelper.setIsoDate(12, now); // local transaction date
isoMsgHelper.setIsoTime(13, now); // local transaction time
...
isoMsgHelper.setIsoDateTime(7, new Date()); // transmission date/time


So far, it has removed a lot of duplicated code. We are expanding ISOMsgHelper to support other commonly used formats. Hope this helps!