Reading the response

The response arrives in the app-to-app-response byte-array extra. The delimiter decodes to
the Unicode replacement character, so normalise before splitting:

String raw = new String(data).replace("\uFFFD", ";");
String[] f = raw.split(";");

Splitting on ; yields a leading empty element. The fields an AFD flow depends on:

IndexFieldWhy AFD needs it
1Transaction type codeConfirms which request is being answered - A4 pre-auth, A7 capture, A9 void
2Response codeThe decision. See below
3Response textDisplay only - never branch on it
8Transaction dateRequired input to Auth Extension and Auth Void
10RRNRequired input to Extension, Void and Reversal. The reference to store
11Approval codeRequired input to Extension and Void
12Terminal ID
27SchemeDecides which capture and void messages you may send - section 4. mada, VISA

Indices 0 to 13 are confirmed against production captures. The rest are read off three aligned
captures - an approval, a decline and a 989.

⚠️

Warning

Nothing should be indexed past 27. A 989 response omits the empty field that
sits at 28 in an approval, so everything after it shifts by one, and a different payment app
build shifts it again. Find what you need in the tail by scanning for its shape, not by its
position.

The scheme in particular is field 27, not 29 - 29 holds CARD, which reads the same for
mada and for Visa. On this page that is not a cosmetic error: field 27 is what decides which
capture and void messages are legal, and a forecourt that reads 29 will send the wrong one.

Three of these — rrn, approvalCode and transactionDate — are not diagnostics. They are
the only handle you have on an open authorisation, which is why section 8 insists they are
written to durable storage before the nozzle is released.

Response codes

CodeMeaningIs the money held or taken?
000ApprovedYes
001, 003, 007, 087, 089ApprovedYes
060Approved — issuer down, mada standing in (STIP)Yes, and you must honour it
061Approved — mada down, POS standing in (STIP)Yes, and you must honour it
400Reversal acceptedThe reversal succeeded
981Reversal time limit expiredThe charge stands. A human settles it
989No response — no card was presentedNo. The host was never asked
anything elseDeclinedNo

060 and 061 are audited AFD scenarios in their own right — see section 7, The scenario matrices. A forecourt that
treats them as declines fails certification and refuses fuel to customers whose payment was in
fact authorised.

Two things that will bite an unattended pump

The result can arrive before your screen does. The payment app is foreground for the whole
transaction, so its answer races your own onResume. If a listener is cleared in onPause and
re-attached in onResume, the response can land in the gap and be dropped. This has been
measured at 28 ms on a terminal running this SDK — the callback went nowhere and the screen
waited until it timed out, for a reversal that had already succeeded. Hold a result that arrives with no
listener and deliver it when one attaches.

Receive at application scope, not in an Activity. There is no onActivityResult here. If an
Activity owns the receiver, a low-memory kill while the payment app is in front loses the
outcome of a transaction that has already placed a hold.


← Talking to the payment app  ·  Contents  ·  The transaction types →


Did this page help you?