Thursday, August 15, 2013

ISO8583 with WSO2 ESB

Introduction

ISO8583 is a messaging standard that is commonly used by banks and financial sector institutes for transactions between devices such as ATMs and switches, as well as for card payments.  Many might call it more of a ‘device-to-device’ protocol but since recently, I have come across situations where several financial institutions were looking for solutions that can provide applications with a more ‘simpler’ northbound API capable of absorbing the complexity of a southbound ISO8583 message and transport.

With message transformation and transport level flexibilities provided by WSO2 ESB, I thought it might be useful for developers and system designers if I could demonstrate how this can be achieved with few configuration artifacts and + java code.

Prerequisites
  • Fair knowledge on WSO2 ESB API configuration
  • Previous experience with creating mediators/transports for WSO2. (Well, even if you haven’t done this before, my attempt is to give you as much insight as possible on how to do this)

Products used

WSO2 ESB – Version 4.7.0


ISO8583 Java ports

Well, I really wasn’t looking to (re)implement a Java API for ISO8583 at this point, hence I did some research about the options available.  I wouldn’t say that I spent enough time analyzing pros and cons of each of these available options, but I thought both jPOS [1] and jISO8583 [2] looked promising.  I opted to use jPOS for this PoC as it had enough documentation I needed for the scenario I wanted to cover.  Also jPOS seems to have adequately addressed the implementation of multiple delivery channels (i.e. BASE24, ASCII, etc.) making it adaptable for organizational specific message formats.

Sample Scenario

Let’s take the scenario of a certain financial application needing to make a credit transaction by sending an XML message that needs to be converted to an ISO8583 byte stream before passed on to the wire through a TCP channel.



Design

In the context of WSO2 ESB, there are multiple ways to do this.  We can write a mediator which could be the option that many would choose, but I wanted to make this slightly more exciting for myself as well as for the readers.  With the idea of a one-to-one XML field mapping to ISO8583 fields, I am going to implement a Transport Sender which will create a jPOS ISOMsg object and serialize into an ASCII Channel that will establish a TCP connection with an ISO8583 server port, deliver the payload and close the socket connection. In this example, I am not going to implement code to receive anything back from the ISO8583 server, hence we will only configure an OUT_ONLY API. If you are to implement a full blown adapter of such nature, you will typically need to implement the following components.

  • Message Builder – Typically for ESB southbound (request) where the northbound payload (XML in this case) is transformed to an ISO8583 payload.
  • Transport Sender – ESB outbound channel where the ISO8583 output stream is sent to the wire typically through a TCP channel.
  • Transport Listener – ESB inbound channel where the ISO8583 binary message is read from the wire typically through a TCP channel.
  • Message Formatter – Typically for ESB northbound (response) where the incoming ISO8583 payload out of the Transport Listener is transformed back to a northbound payload (XML in this case).


Implementation

First, we need to define our ISO8583 field definition.  This might be a bit confusing to some.  If we are dealing with a specification, why do we need a field definition?  This is because that ISO8583 specification is not hard-binding any data elements and/or field ordering. It is entirely up to the application designer to define which field types/IDs need to be placed for their specific transnational requirements.

At a glance, the field definition file looks like the following.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE isopackager SYSTEM "genericpackager.dtd">
<isopackager>
  <isofield
      id="0"
      length="4"
      name="Message Type Indicator"
      class="org.jpos.iso.IFA_NUMERIC"/>
  <isofield
      id="1"
      length="16"
      name="Bitmap"
      class="org.jpos.iso.IFA_BITMAP"/>
  <isofield
      id="2"
      length="19"
      name="Primary Account number"
      class="org.jpos.iso.IFA_LLNUM"/>
  <isofield
      id="3"
      length="6"
      name="Processing Code"
      class="org.jpos.iso.IFA_NUMERIC"/>
</isopackager>

Please refer to [3] & [4] for a complete reference of ISO8583.   As per now, let me just say that each field should have an ID, a length and type specified in its definition.  I have only listed a snippet of the XML config here, and you may find the full definition jposdef.xml inside the codebase.
I have created a simple maven project to implement this transport.  Make sure that you have included the jPOS dependencies on pom.xml as follows.
              <dependency>
                     <groupId>org.jpos</groupId>
                     <artifactId>jpos</artifactId>
                     <version>1.9.0</version>
              </dependency>
              <dependency>
                     <groupId>com.sleepycat</groupId>
                     <artifactId>je</artifactId>
                     <version>4.0.92</version>
             </dependency>


To implement the transport sender, you need to subclass the AbstractTransportSender and implement its sendMessage method as follows.
public class ISO8583TransportSender extends AbstractTransportSender {

       @Override
       public void sendMessage(MessageContext msgCtx, String targetEPR,
                     OutTransportInfo outTransportInfo) throws AxisFault {

              try {

                     ISOMsg isoMsg = toISO8583(msgCtx);

                     URI isoURL = new URI(targetEPR);

                     ISOPackager packager = new GenericPackager(this.getClass()
                                  .getResourceAsStream("jposdef.xml"));

                     ASCIIChannel chl = new ASCIIChannel(isoURL.getHost(),
                                  isoURL.getPort(), packager);
                    
                     chl.connect();
                     chl.send(isoMsg);
                     chl.disconnect();

              } catch (Exception e) {
                     throw new AxisFault(
                                  "An exception occurred in sending the ISO message");
              }
    }
}

As I mentioned before, in a full blown implementation, you need to split the message builder/formatter from the transport sender and receiver logic.  Largely due to the fact that I am using the jPOS library for both ISO message encapsulation as well as for streaming through the TCP channel, I am going to implement the following logic in my transport sender class itself.
A new API needs to be configured on WSO2 ESB to accept our application friendly northbound XML payload.

   <api name="iso" context="/iso">
      <resource methods="POST">
         <inSequence>
            <property name="OUT_ONLY" value="true"/>
            <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
            <send>
               <endpoint name="isoserver">
                  <address uri="iso8583://localhost:5000"/>
               </endpoint>
            </send>
            <drop/>
         </inSequence>
         <outSequence>
            <drop/>
         </outSequence>
      </resource>
   </api>

So what we are doing here is exposing an API endpoint to northbound applications to send an XML payload which will be sent to an endpoint of type iso8583.  So how does the WSO2 ESB configuration know exactly what this iso8583:// is?  For this, we need to put the following entry on axis2.xml under $ESB_HOME/repository/conf/axis2 which will instruct the ESB core to pick our transport sender for an endpoint of iso8583 type.

<transportSender name="iso8583" class="org.wso2.iso8583.transport.ISO8583TransportSender"/>

I am going to keep it simple and will be sending the following XML from the northbound API in this sample scenario, but this is by no means a proper XML message structure you might want to use for your real EAI scenario.
<iso8583message>
       <config>
              <mti>1800</mti>
       </config>
       <data>
              <field id="3">110</field>
              <field id="5">4200.00</field>
              <field id="48">Simple Credit Transaction</field>
              <field id="6">645.23</field>
              <field id="88">66377125</field>
       </data>
</iso8583message>

In simple terms, what I have here is a set of values defined against several field types specified in jposdef.xml.  
Going back to our code, the first thing we will do is to convert the XML payload to an ISOMsg object.

       public ISOMsg toISO8583(MessageContext messageContext) throws AxisFault {
              SOAPEnvelope soapEnvelope = messageContext.getEnvelope();
              OMElement isoElements = soapEnvelope.getBody().getFirstElement();

              ISOMsg isoMsg = new ISOMsg();

              @SuppressWarnings("unchecked")
              Iterator<OMElement> fieldItr = isoElements.getFirstChildWithName(
                           new QName(ISO8583Constant.TAG_DATA)).getChildrenWithLocalName(
                           ISO8583Constant.TAG_FIELD);

              String mtiVal = isoElements
                           .getFirstChildWithName(new QName(ISO8583Constant.TAG_CONFIG))
                           .getFirstChildWithName(new QName(ISO8583Constant.TAG_MTI))
                           .getText();

              try {
                     isoMsg.setMTI(mtiVal);

                     while (fieldItr.hasNext()) {

                           OMElement isoElement = (OMElement) fieldItr.next();

                           String isoValue = isoElement.getText();

                           int isoTypeID = Integer.parseInt(isoElement.getAttribute(
                                         new QName("id")).getAttributeValue());

                           isoMsg.set(isoTypeID, isoValue);

                     }

                                  return isoMsg;


If you want to get a deeper understanding of AXIOM, you should perhaps refer to [5].  As per our scenario, what I am doing here is to iterate through the list of fields coming from northbound payload (encapsulated within an OMElement object), and populate the ISOMsg object.  Further, I am using the config section of my XML payload to encapsulate the request/response level meta-data for my simple transaction.  I am sure there is more to that if you read the full ISO8583 spec, but it is not the intention to cover that all here.
Now that we have our ISOMsg object populated, the next step is to initialize a “Packager” instance that corresponds to our ISO8584 field definition.


ISOPackager packager = new GenericPackager(this.getClass().getResourceAsStream("jposdef.xml"));

Ok, I guess we are all set to send it down the wire now.  As a matter of fact, the whole purpose of the Transport Sender is to do exactly that, so let’s use the jPOS API to do that for us.  Again, if you are writing your own socket level code, this part will be a bit more than just 3-4 lines of code.

ASCIIChannel chl = new ASCIIChannel(isoURL.getHost(),
                                  isoURL.getPort(), packager);
                    
                     chl.connect();
                     chl.send(isoMsg);
                     chl.disconnect();

So for this sample, I am using the ASCIIChannel to send my ISO payload to the server, and please feel free to explore the other delivery channels provided by jPOS when you run this code.
So… where is our ISO server then? Thankfully, jPOS provides a mock server implementation to test our scenario, and typically there should not be any change to the code if you have the option of connecting to a real ISO8583 server endpoint.   Well… I am not at all suggesting you should try and make a credit card transaction on a real production environment whilst testing this code.

       static final String hostname = "localhost";
       static final int portNumber = 5000;


       public static void main(String[] args) throws ISOException {
             

              ISOPackager packager = new GenericPackager("jposdef.xml");
              ServerChannel channel = new ASCIIChannel(hostname, portNumber, packager);
              ISOServer server = new ISOServer(portNumber, channel, null);

              server.addISORequestListener(new MockISO8583Server());

              System.out.println("ISO8583 server started...");
              new Thread(server).start();
       }




Normally I use the Advanced REST client on Chrome to send my XML payload, but you may use your favorite REST client to fire the request to the API endpoint we’ve configured on the WSO2 ESB.  Just make sure that you have the MockISO8583Server running before you do this.


If everything goes well, you should see the following output on you MockISO8583Server console.

ISO8583 incoming message on host [127.0.0.1]
ISO8583 Message received...
----ISO MESSAGE-----
  MTI : 1800
    Field-3 : 000110
    Field-5 : 000004200.00
    Field-6 : 000000645.23
    Field-48 : Simple Credit Transaction!!
    Field-88 : 0000000066377125
--------------------

You can download the codebase from here

What’s next?

Well, technically speaking, the pattern we have used here should be adaptable to most banking/financial scenarios and messaging protocols. In my next post, I am planning to cover an implementation of a Transport Listener to handle the responses back from the ISO8583 endpoint and presenting as an XML payload to the northbound.

References



8 comments:

  1. Hi,

    When will you able to publish response handling part ?

    Thanks
    Sampath

    ReplyDelete
  2. Thanks, this is very useful aticle ^_^ will follow you

    ReplyDelete
  3. hi manoj thanks for information. can u give me some hints of getting the response from the server as well??

    ReplyDelete
  4. Hi,

    I want to integrate some Banking Institute with out POS Network where i need professional support, can you provide us.

    Please contact me on : mehulsbhatt@hotmail.com

    ReplyDelete
  5. hi manoj, thanks for the tutorial, how to running MockISO8583Server?

    thanks

    ReplyDelete
  6. Hi Manoj ,

    I got a use case where I want some wso2 component to call HSM hardware for ISO-1 PIN block format when we send normal text pin.

    ReplyDelete
  7. Hello there,
    it's been a while since this was posted, But was any one able to handle the ISO8583 response or was able to prepare the message Builder?

    ReplyDelete