What your application must survive

A hold outlives your process. The customer may pump for minutes. The terminal may be power-cycled, crash, or lose the network in between.

Write the rrn, approvalCode and transactionDate of an open pre-authorisation to durable storage before the nozzle is released, and recover them on startup. Every subsequent message needs them:

TransactionRequest data
Pre AuthorizationdateTimeStamp;authAmount;etpInput;ecrRefNo!
Pre Auth ExtensiondateTimeStamp;rrn;transactionDate;approvalCode;etpInput;ecrRefNo!
Pre Auth VoiddateTimeStamp;authAmount;rrn;transactionDate;approvalCode;etpInput;ecrRefNo!
Purchase Advice / CapturedateTimeStamp;amount;rrn;transactionDate;approvalCode;captureType;etpInput;ecrRefNo!
Partial ReversaldateTimeStamp;amount;etpInput;ecrRefNo!
Suspect ReversaldateTimeStamp;etpInput;ecrRefNo!

The capture request

Capture is the one that settles the money, and it is worth setting out field by field. A
worked example, txType = 4:

130826111824;100;622523000317;130826;123456;1;0;12345678000234!
#ValueField
1130826111824dateTimeStamp — now, as ddMMyyHHmmss
2100amount — the fuel actually delivered, in minor units. SAR 1.00 here
3622523000317rrn — from the pre-authorisation response, index 10
4130826transactionDate — the pre-authorisation's date, ddMMyy
5123456approvalCode — from the pre-authorisation response, index 11
61Capture type1 is a final capture. See below
70etpInput — the print flag. 0 disables printing
812345678000234ecrRefNo — your own reference for the capture

Fields 3, 4 and 5 all come from the pre-authorisation you are settling. That is the concrete
reason section 8 insists they are written to durable storage before the nozzle is released:
without all three, a delivered tank cannot be charged for.

Note the amount is the delivered figure, not the authorised one. Sending the authorised
amount back would charge every customer for a full tank regardless of what they took.

Final capture and partial capture

Field 6 selects between them:

Field 6Capture typeIn scope?
1Final — settles the authorisation and closes itYes. The only value mada uses
0Partial — settles part of the hold, leaving it openNo. Withdrawn under SAMA requirements

On mada, field 6 is always 1. Partial capture is in the message format and is not
available to you: it has been withdrawn, and since SAMA is certifying mada only, there is no
current scenario in which 0 is correct. It is documented here so that a 0 seen in an older
integration or a vendor sample is recognised rather than copied.

Do not read that as "capture the full authorised amount". A final capture for less than the
hold is the ordinary outcome of almost every fill, and the issuer releases the difference; that
is what the SAMA framework means by "either equal to the pre-authorized amount or less". What
has gone is the mechanism for settling one authorisation in instalments, not the ability to
settle it for a smaller figure.

Field 7 is 0 on a forecourt. An AFD terminal has no printer. The customer's receipt is the
QR code on the authorisation screen, which is why that screen offers QR code or no
receipt
and nothing else.

The partial reversal

🚧

Important

Visa only, and outside the current scope. This is how a Visa
authorisation is settled for less than the hold, because Visa does not accept a lesser
capture. With SAMA certifying mada only, nothing in your mada flow sends one.

Confirmed against production traffic, where the payment app logs the fields it parsed:

[, C6, 000000000002, 200526080254, 12345678000108, 0, <signature>, 120]

The request that produces it:

dateTimeStamp;amount;etpInput;ecrRefNo!

Structurally it is a pre-authorisation: an amount, a timestamp, your reference and the print
flag. No RRN, which means — like the suspect reversal below — it applies to the payment
app's last transaction rather than one you name, so ordering matters.

The amount is what was not dispensed

This is the trap, and it is the opposite of everything else in the guide.

A capture carries the fuel delivered. A partial reversal carries the fuel not delivered
the unused part of the hold, which is released back to the cardholder.

AuthorisedDeliveredAmount in the message
mada — CaptureSAR 300SAR 12012000 — what was pumped
Visa — Partial ReversalSAR 300SAR 12018000 — what was not

Same forecourt, same fill, two schemes, and the figure on the wire is inverted between them.
Reuse the capture's amount for a Visa partial reversal and you release the SAR 120 the customer
owes while charging them the SAR 180 of fuel they never took — and it will settle cleanly,
because both numbers are valid amounts against that hold.

Compute it as authorised − delivered, from the authorised figure you stored, and never by
reaching for the variable holding the capture amount.

The suspect reversal

🚧

Important

Descoped for mada. The suspect transaction is a Visa and Mastercard
mechanism, and with SAMA certifying mada only, it is not part of the current scope. Nothing
in your mada flow should send one. It is documented because the 120-second row of the void
matrix is where it belongs once other schemes come into scope, and because a terminal that
handles more than mada will meet it.

The message for the 120-second unknown timeout, where the terminal gives up with no idea what
happened. It is short:

110826121030;0;12345678000089!

dateTimeStamp, etpInput, ecrRefNo. That is all — no RRN and no amount.

That is not an omission, and it is the one thing to understand about this message. It carries no
reference because the payment app resolves the transaction itself, looking up its own record
of the last online transaction and taking the RRN and approval code from there. Which is the
only design that could work: a suspect reversal is sent precisely when you never received a
response, so you have no reference to quote.

🚧

Important

It follows that a suspect reversal is positional. It reverses whatever the
payment app did last, not a transaction you name. Send it before anything else transacts on
that terminal, and never as a tidy-up at the end of a shift — by then it will reverse the
wrong sale, and it will report success while doing so.

It is sent by the ordinary route — getPackData(reqData, 28, signature) and the same Intent
handoff as everything else. There is nothing special about the transport.

An authorisation your application has forgotten is money held against a customer's card that nothing will ever release. It will not fail loudly; it will sit there for 24 hours and then quietly expire, having taken fuel and charged nobody.

Reconcile what was never closed. Something has to find pre-authorisations with no capture and no void and resolve them. This is not the same problem as a failed sale on an attended terminal, where it is over inside a minute with a person standing there. A forecourt hold may need clearing hours later, on a terminal that has been power-cycled since, for a customer who left long ago.

Pump control is not in this protocol. Releasing and metering the nozzle is an FCS integration and has nothing to do with the ECR SDK. What the two systems must agree on is which authorisation belongs to which pump — and that mapping has to survive a restart of either side.


← The scenario matrices  ·  Contents  ·  Certification →


What’s Next

Did this page help you?