When Your Remote Auth Needs Don't Conform to ISO8583
As may you know from previous my posts here, when Alejandro originally envisioned jPOS, he did so in part to solve the frustrating, error-prone, time-consuming (and often mind-numbing) process of building ISO8583-based payment system interfaces. From the kernel of that original idea, he's evolved jPOS to become Your Complete Payment System Solution Framework. Nowadays, things have come full circle - people (including me) often pop up with the need to build specific payment interfaces that aren't ISO8583-based. For example, we have a customer with a terminal interface (servicing 4,000+ multi-lane locations shooting us close to one million authorization attempts per day) that uses Visa Gen 2 ('VG2'). And, we've just implemented a remote authorization link to Verizon (formally MCI) that is waaaay non-ISO. What these interfaces - and others like it - have in common is that they're Field Separator Delimited ('FSD')-style implementations. In other words, instead of being bit-mapped, they're defined by some type of fixed format. 'Fixed' here is a loose term. The VG2 interface, for example, has some message-to-message variance, but its essence is that by knowing a few key variables, you can know/define the appropriate message structure. I've blogged in the past about jPOS' FSD facility, which is what I call the unsung hero of the jPOS framework - Here I talk about how to implement a Thales HSM interface in FSD...and here's where you'd use the same facility to implement an extract/settlement subsystem. Now for a remote authorization interface that can be implemented as FSD, Alejandro has fashioned a 'jPOS extension' called FSDISOMsg - it leverages the full power of the jPOS architecture in a nice way. For example, a client contacted Alejandro and me the other day and told us about their new interface requirement - it was to implement a remote auth solution to a Trintech-based system. Trintech's IPSX specification is something that can be implemented using the FSDISOMsg approach. To explain it to our client, I showed them how we did it recently to certify and implement the Verizon interface. You mentioned you’ll have a new (outgoing) interface – it’ll be the first external interface on the new system and will be FSD-based. We recently faced the same situation. The Verizon Interface (a Phone Card) is also an FSD interface. Alejandro created “FSDISOMsg” as a jpos_extension. Here's the linchpin of the approach: The QMUX key fields have to be named with their ISO equivalents. You can see below that we have two fields that equate directly to 41 and 11 (QMUX's default key - the TID and STAN respectively), so we named them as such in the XML structure. Anything you put in the mux key you need to tie in here. The field that designates ‘tran-type’ we named as ‘0’ (not mandatory) so that we could note that it’s the MTI-like equivalent We named the Response Code-like field “39” so as not to break some OLS.Switch (our jPOS-based implementation) internals on response logging and origin-side formatting of replies. <mux class="org.jpos.q2.iso.QMUX" logger="Q2" name="verizon-mux"> ...and our outgoing request definition (implemented as verizon-base.xml) is an FSD XML definition that looks like this... <schema> ...and the response from Verizon (implemented as verizon-resp-base.xml) looks like this: <schema> ...and our program that ties everything together looks like this... public class CreateVerizonRequest extends CreateISORequest {
<in>verizon-receive</in>
<out>verizon-send</out>
<ready>verizon.ready verizon1.ready</ready>
<key>11, 41</key>
</mux>
<field id="header" type="K" length="4" >PREQ</field>
<field id="msg-version" type="K" length="2" >40</field>
<field id="0" type="N" length="2" /><!-- tran-type -->
<field id="length-of-rest" type="K" length="3" >159</field>
<field id="POSC-routing-info" type="K" length="16">* EYECATCHER *</field>
<field id="request-source" type="N" length="1" />
<field id="acq-cust-id" type="K" length="5" >12345</field>
<field id="division-id" type="K" length="2" >00</field>
<field id="store-number" type="A" length="6" />
<field id="41" type="A" length="16"/><!-- term-id -->
<field id="11" type="N" length="6" /><!-- seqno -->
<field id="request-date" type="N" length="8" />
<field id="request-time" type="N" length="6" />
<field id="purchase-price" type="N" length="6" />
<field id="request-units" type="N" length="4" />
<field id="request-units-frac" type="K" length="2" >00</field>
<field id="track-data" type="A" length="80"/>
<field id="user-context-ind" type="K" length="1" >N</field>
</schema>
<field id="header" type="A" length="4" />
<field id="msg-version" type="N" length="2" />
<field id="0" type="N" length="2" />
<field id="length-of-rest" type="N" length="3" />
<field id="POSC-routing-info" type="A" length="16"/>
<field id="store-number" type="A" length="6" />
<field id="41" type="A" length="16"/><!-- term-id -->
<field id="11" type="N" length="6" /><!-- seqno -->
<field id="request-date" type="N" length="8" />
<field id="request-time" type="N" length="6" />
<field id="request-source" type="N" length="1" />
<field id="filler-1" type="A" length="2" />
<field id="refund-amount" type="N" length="6" />
<field id="reply-units" type="N" length="6" />
<field id="completion-status" type="N" length="2" />
<field id="39" type="A" length="3" /><!-- reason-code -->
<field id="pin-status" type="A" length="2" />
<field id="exp-date" type="N" length="8" />
<field id="access-num" type="N" length="10"/>
<field id="balance" type="N" length="6" />
<field id="user-context-ind" type="A" length="1" />
</schema>
protected ISOMsg createISOMsg(Context ctx) throws Exception {
Date now = (Date) ctx.get (TIMESTAMP);
FSDMsg fsd = new FSDMsg ("file:cfg/verizon-");
FSDMsg msg = (FSDMsg) ctx.tget (REQUEST);
assertNotNull (msg, APPLERR_BADDATA, "CreateVerizonRequest: invalid VG2 request");
fsd.set ("0", mti);
fsd.set ("request-source", "0");
String storeNumber = msg.get ("store-number");
fsd.set ("store-number", storeNumber);
StringBuffer sb = new StringBuffer();
sb.append (storeNumber);
sb.append (msg.get ("terminal-number"));
sb.append (msg.get ("register-number"));
sb.append ("00000");
fsd.set ("41", sb.toString());
long l = SpaceUtil.nextLong (sp, "VZNSTAN") % 1000000L;
if (l == 0) // deal with wrap-around
l = SpaceUtil.nextLong (sp, "VZNSTAN") % 1000000L;
fsd.set ("11", ISOUtil.zeropad (Long.toString(l), 6));
fsd.set ("request-date", ISODate.formatDate (now, "yyyyMMdd"));
fsd.set ("request-time", ISODate.formatDate (now, "HHmmss"));
fsd.set ("purchase-price", msg.get ("amount"));
String track2 = (String) ctx.tget (VZN_TRACK_DATA);
String units = (String) ctx.tget (VZN_UNITS);
String type = (String) ctx.tget (VZN_TYPE);
assertNotNull (msg, APPLERR_BADDATA, "Verizon TRACK DATA not present");
assertNotNull (msg, APPLERR_BADDATA, "Verizon UNITS not present");
assertNotNull (msg, APPLERR_INVCARD,
"cannot compute request-units from magnetic-strip-info ("+track2+")");
fsd.set ("request-units", units);
fsd.set ("track-data", track2);
return new FSDISOMsg (fsd);
}
}
Comments