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:
| Index | Field | Why AFD needs it |
|---|---|---|
| 1 | Transaction type code | Confirms which request is being answered - A4 pre-auth, A7 capture, A9 void |
| 2 | Response code | The decision. See below |
| 3 | Response text | Display only - never branch on it |
| 8 | Transaction date | Required input to Auth Extension and Auth Void |
| 10 | RRN | Required input to Extension, Void and Reversal. The reference to store |
| 11 | Approval code | Required input to Extension and Void |
| 12 | Terminal ID | |
| 27 | Scheme | Decides 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
| Code | Meaning | Is the money held or taken? |
|---|---|---|
000 | Approved | Yes |
001, 003, 007, 087, 089 | Approved | Yes |
060 | Approved — issuer down, mada standing in (STIP) | Yes, and you must honour it |
061 | Approved — mada down, POS standing in (STIP) | Yes, and you must honour it |
400 | Reversal accepted | The reversal succeeded |
981 | Reversal time limit expired | The charge stands. A human settles it |
989 | No response — no card was presented | No. The host was never asked |
| anything else | Declined | No |
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 →
Updated 3 days ago
