Reversal: One Nuance That Actually Matters

Table of contents

This is the first of two short unhappy-path posts following the approved payment walkthrough – same mock, same Postman collection, same PIN-entry mechanic for the original payment this reverses. Setup is already covered in the mock terminal post; not repeated here.

Objective

Reverse a previously authorised payment, confirm the response shape, and check what the mock actually validates about the transaction being reversed.

Request sent

POST {baseUrlMock}/sync, MessageCategory: Reversal, referencing the prior Payment’s identifiers (auto-filled by the collection’s chaining variables, confirmed populated in the previous post):

"ReversalRequest": {
  "ReversalReason": "MerchantCancel",
  "OriginalPOITransaction": {
    "POIID": "{poiId}",
    "POITransactionID": {
      "TransactionID": "{lastTransactionId}",
      "TimeStamp": "{lastTimestamp}"
    }
  }
}

Actual result

200 OK, immediate (no PIN-entry wait – only Payment requests block on that):

{
  "SaleToPOIResponse": {
    "ReversalResponse": {
      "POIData": { "POITransactionID": { "TimeStamp": "2013-06-12T12:08:36+00:00", "TransactionID": "21f1268f-9126-4bce-b127-9c2d5ffa024e" } },
      "Response": { "Result": "Success", "AdditionalResponse": ".+" },
      "PaymentReceipt": [ "fake cashier + cardholder receipt, TOTAL €10.99, card ending 9990" ]
    },
    "MessageHeader": { "SaleID": "SALE_ID_42", "ServiceID": "1234567890AB", "POIID": "V400m-123456789", "MessageCategory": "Reversal", "MessageType": "Response" }
  }
}

Same static-fixture pattern as the approved payment – full proof already in the previous post’s appendix; not repeated here.

Screenshot

Postman: reversal request and response

Request body showing {lastTransactionId}/{lastTimestamp} referencing the prior Payment, response 200 OK in 3ms (no PIN wait), Result: Success.

Test mode vs. real life

The important nuance here is ID reuse. In this test, OriginalPOITransaction.POITransactionID (TransactionID + TimeStamp) is populated automatically from the last Payment, but confirmed in the mock’s own routing code that it’s never actually checked – the mock returns Success for a Reversal referencing any value, including one that never existed.

In real life, this is exactly backwards: OriginalPOITransaction must precisely match a real, previously-authorised transaction on that terminal – it’s how the terminal (and Adyen behind it) identifies which transaction to reverse. Get it wrong and a real terminal returns an error (e.g. “original transaction not found”), not a silent success. TransactionID/TimeStamp from a genuine prior authorisation are mandatory inputs, not decorative fields – same category as ServiceID in the main flow, just enforced at a different layer (the terminal/Adyen, not the message schema).

Also worth knowing generally (not verified against this mock, since it doesn’t implement any timing logic at all): real reversals are typically only valid for a limited window – before the original transaction is captured/settled. Once settled, a refund is needed instead, which is a different message/flow entirely. Worth confirming against Adyen’s own docs when this becomes relevant against a real terminal.

Net takeaway: this test confirms the request/response shape for Reversal and that the collection’s chaining variables wire up correctly – it does not, and cannot, tell us anything about real transaction-matching behaviour. Familiarization with the message contract only.

See also

Insufficient balance decline – the other short unhappy-path scenario in this series, using the same approved-payment request with one digit changed.

Until next time, peace and love!


2024 Shafik Walakaka. Please submit feedback and comments here!