# Feature: Reversal Transaction

**Transaction Type:** Automatic Reversal / POS-Initiated Reversal
**Processor Class:** `PosReversalTransactionProcessorImpl`, `ReversalService`
**Transaction Type Enum:** `TxnType.VOID` (mapped from MTI 0400/0420)

---

## 1. Overview

A Reversal informs the bank acquirer to cancel a transaction for which no response was received, or whose response was lost before the middleware could record it. The POS terminal may also explicitly send a reversal (MTI 0400) if it detects a timeout on its side.

**Reversal is triggered automatically by the middleware in these scenarios:**
1. `SocketTimeoutException` during `acquirerChannel.transceive()` -> `handleResponseTimeout()`
2. Null response from acquirer -> `handleConnectionLoss()`
3. Database error after successful bank response -> `handleDatabaseError()`
4. Stale temp transaction on startup -> `cleanupOrphanedTransactions()`

**Reversal is also received from the POS terminal:**
5. POS-initiated reversal (MTI 0400) received directly from the terminal

The middleware supports up to 3 retry attempts at 60-second intervals. A reversal is considered successful when DE39 is `00`, `21`, or `56`.

---

## 2. Flow Diagrams

### 2a. Auto-Reversal (Middleware-Initiated on Timeout)

```
POS Terminal         jPOS Middleware                              Bank Acquirer
     |                      |                                           |
     |-- MTI 0200 --------->|                                           |
     |                      |--- MTI 0200 (Sale) ---------------------->|
     |                      |    [timeout: SocketTimeoutException]       |
     |                      |<-- (no response) --------------------------|
     |                      |                                           |
     |                      | handleResponseTimeout():                  |
     |                      |   updateTempStatus(RESPONSE_TIMEOUT)      |
     |                      |   createReversalRequest(REASON_TIMEOUT)   |
     |                      |   saveReversalRecord() -> pos_transaction_reversal
     |                      |                                           |
     |                      |--- MTI 0400 (reversal, DE90) ----------->|
     |                      |<-- MTI 0410 (DE39=00) -------------------|
     |                      |                                           |
     |                      | atomicTempCleanupWithReversalRecord():    |
     |                      |   delete pos_temp_transaction             |
     |                      |   update pos_transaction_reversal COMPLETED
     |                      |                                           |
     |<-- MTI 0210 (DE39=83)|                                           |
```

### 2b. POS-Initiated Reversal (MTI 0400 from Terminal)

```
POS Terminal                jPOS Middleware                          Bank Acquirer
     |                             |                                        |
     |-- MTI 0400 (DE3=000000) --->|                                        |
     |    DE47={"origTrace":"000257"} |                                     |
     |                             |-- preprocessReversal()                 |
     |                             |   1. check pos_transaction_reversal    |
     |                             |   2. check pos_failed_transaction      |
     |                             |   3. check pos_temp_transaction        |
     |                             |   4. check pos_transaction             |
     |                             |-- handleReversalForApprovedTxn()       |
     |                             |   createReversalRequest(CONNECTION_LOST)
     |                             |--- MTI 0400 (bank TID/MID) ----------->|
     |                             |<-- MTI 0410 (DE39=00) -----------------|
     |                             |-- atomicTransactionCleanupWithReversalRecord()
     |                             |   move pos_transaction -> pos_failed_transaction
     |<-- MTI 0410 (DE39=00) ------|                                        |
```

---

## 3. ISO-8583 Message Specification

### 3.1 Reversal Request Fields (Middleware -> Acquirer, MTI 0400)

| DE # | Field Name                          | Format        | M/C | Description / Source                                         |
|------|-------------------------------------|---------------|-----|--------------------------------------------------------------|
| MTI  | Message Type Indicator              | BCD F4        | M   | `0400` (or `0420` for reversal advice)                       |
| 2    | PAN                                 | BCD LLVAR     | M   | Decrypted from `PosTransactionReversal.originalEncryptedPan` |
| 3    | Processing Code                     | BCD F6        | M   | Same as original: `000000` (Sale reversal)                   |
| 4    | Transaction Amount                  | BCD F12       | M   | Same as original: `000000006500`                             |
| 11   | STAN                                | BCD F6        | M   | Bank STAN from original: `000257`                            |
| 12   | Local Transaction Time              | BCD F6        | M   | `reversal.initiatedDateTime` formatted as `HHmmss`           |
| 13   | Local Transaction Date              | BCD F4        | M   | `reversal.initiatedDateTime` formatted as `MMdd`             |
| 14   | Card Expiry Date                    | BCD F4        | C   | Decrypted from `originalEncryptedExpiryDate`                 |
| 19   | Acquiring Institution Country Code  | BCD F3        | M   | From acquirer config                                         |
| 22   | POS Entry Mode                      | BCD F3        | M   | `reversal.originalEntryMode` (e.g., `051`)                   |
| 23   | PAN Sequence Number                 | BCD F3        | C   | `reversal.originalPanSequence`                               |
| 24   | NII                                 | BCD F3        | M   | From acquirer config                                         |
| 37   | RRN                                 | ASCII F12     | M   | `reversal.originalReferenceNo`                               |
| 41   | Card Acceptor Terminal ID           | ASCII F8      | M   | Bank TID (`reversal.bTid`)                                   |
| 42   | Card Acceptor Merchant ID           | ASCII F15     | M   | Bank MID (`reversal.bMid`)                                   |
| 47   | Additional Data (JSON)              | ASCII LLLVAR  | M   | `{"origMti":"0200","origTrace":"000257","origDate":"...","origTime":"..."}` |
| 49   | Currency Code                       | BCD F3        | M   | `reversal.originalCurrencyCode`                              |
| 62   | Batch Number                        | ASCII LLLVAR  | M   | `reversal.originalBatchNo`                                   |
| 63   | Private Data                        | ASCII LLLVAR  | M   | `"JPOS_REVERSAL"` (hardcoded marker)                         |
| 90   | Original Data Elements              | BCD F42       | M   | `origMTI(4) + origSTAN(6) + origDate(4) + origTime(6) + 00000000000` |

> **DE-90 example (from log):** `0200000172041413060100000000000`
> Format: `0200` (MTI) + `000172` (STAN) + `0414` (date) + `130601` (time) + `00000000000` (padding)

### 3.2 Reversal Response Fields (Acquirer -> Middleware)

| DE # | Field Name               | Format    | M/C | Description                        |
|------|--------------------------|-----------|-----|------------------------------------|
| MTI  |                          | BCD F4    | M   | `0410` (reversal response)         |
| 3    | Processing Code          | BCD F6    | M   | Same as request                    |
| 11   | STAN                     | BCD F6    | M   | Original STAN                      |
| 12   | Local Time               | BCD F6    | M   | Response time                      |
| 13   | Local Date               | BCD F4    | M   | Response date                      |
| 37   | RRN                      | ASCII F12 | M   | Reference number                   |
| 38   | Authorization Code       | ASCII F6  | C   | Present on approval                |
| 39   | Response Code            | ASCII F2  | M   | `00` = Reversal accepted           |
| 41   | Terminal ID              | ASCII F8  | M   | Bank TID                           |

### 3.3 POS Response for POS-Initiated Reversal

| DE # | Field Name   | Value  | Notes                                              |
|------|--------------|--------|----------------------------------------------------|
| MTI  |              | `0410` | Response to POS MTI 0400 request                   |
| 39   | Response Code| `00`   | Reversal completed; `21` if no-op (already declined)|

---

## 4. Processing Codes

| Processing Code | Meaning                                          |
|-----------------|--------------------------------------------------|
| Same as original| Reversal inherits the processing code of the original transaction |
| `000000`        | Reversal of a Sale                               |
| `200000`        | Reversal of a Refund                             |

---

## 5. Response Codes

| DE39   | Meaning                    | Middleware Handling                                    |
|--------|----------------------------|--------------------------------------------------------|
| `00`   | Reversal Accepted          | `ReversalStatus.COMPLETED`; temp cleaned up            |
| `21`   | No Action Taken            | Treated as success (bank has no record)                |
| `56`   | No Card Record             | Treated as success                                     |
| Other  | Reversal Failed            | `scheduleReversalRetry()` up to 3 attempts             |
| (null) | Timeout / No Response      | Reversal marked FAILED; retry scheduled                |

---

## 6. Reversal Status Lifecycle

```
PENDING -> SENT -> COMPLETED              (success)
                -> FAILED -> RETRY_SCHEDULED -> SENT -> ...
                                          -> MAX_RETRIES_EXCEEDED -> MANUAL_REVIEW
```

| Status                  | Description                                              |
|-------------------------|----------------------------------------------------------|
| `PENDING`               | Record created in `pos_transaction_reversal`             |
| `SENT`                  | MTI 0400 dispatched to bank                              |
| `COMPLETED`             | DE39=00/21/56 received; temp cleaned up                  |
| `FAILED`                | Non-success response received                            |
| `RETRY_SCHEDULED`       | Next retry at `now + 60 seconds`                         |
| `MAX_RETRIES_EXCEEDED`  | 3 attempts exhausted                                     |
| `MANUAL_REVIEW`         | Operations team must intervene                           |

---

## 7. POS-Initiated Reversal — Decision Tree

`preprocessReversal()` checks tables in this order:

1. **`pos_transaction_reversal`** (by POS TID/MID + STAN):
   - Status in `REVERSAL_COMPLETED_STATUSES` -> return DE39=`00` immediately (idempotent)
   - Status in `REVERSAL_PENDING_STATUSES` -> `processReversal()` to retry
2. **`pos_failed_transaction`** (original was declined): return DE39=`00` (nothing to reverse)
3. **`pos_temp_transaction`** (transaction in progress):
   - If txnType=VOID -> DE39=`99` (retry later)
   - Otherwise -> `handleResponseTimeout()` to trigger auto-reversal
4. **`pos_transaction`** (original approved): `handleReversalForApprovedTxn()` -> bank call
5. **Not found anywhere**: return DE39=`00` (nothing to reverse)

---

## 8. Retry Configuration

| Parameter                              | Default | Description                         |
|----------------------------------------|---------|-------------------------------------|
| `reversal.response.timeout.seconds`    | 30      | Wait time before declaring timeout  |
| `reversal.retry.max.attempts`          | 3       | Maximum reversal retry count        |
| `reversal.retry.delay.seconds`         | 60      | Interval between retry attempts     |
| `reversal.stale.transaction.threshold` | 45      | Seconds before a temp txn is stale  |
| `startup.cleanup.age.threshold.minutes`| 5       | Orphaned temp txn cleanup on boot   |

---

## 9. Error Handling

| Scenario                         | Behaviour                                                          |
|----------------------------------|--------------------------------------------------------------------|
| SocketTimeoutException on 0200   | `handleResponseTimeout()` -> async MTI 0400; POS gets DE39=83      |
| Null response from acquirer      | `handleConnectionLoss()` -> wait -> MTI 0400                       |
| Reversal bank call fails         | `scheduleReversalRetry()` with max 3 attempts                      |
| 3 retries all fail               | `MAX_RETRIES_EXCEEDED` -> `MANUAL_REVIEW`                          |
| DB error in cleanup              | `alertOperations()` logs CRITICAL; manual intervention required     |
| Stale temp on startup            | `cleanupOrphanedTransactions()` (@PostConstruct, 5-min threshold)  |

---

## 10. BDD Scenarios

### Scenario 1: Auto-Reversal on Timeout — Succeeds on Second Attempt

```
Given a Sale (STAN=000261, amount=AED 50,000.00) was sent to the acquirer
And the acquirer connection timed out (SocketTimeoutException)
Then handleResponseTimeout() is called
And a PosTransactionReversal record is created with status=PENDING
And MTI 0400 is sent to the bank with DE11=000261, DE90=0200000172041413060100000000000
And the bank does not respond (attempt 1) -> status=FAILED, retryCount=1
And after 60 seconds, retry sends MTI 0400 again
And the bank responds MTI 0410 with DE39=00
And pos_temp_transaction is deleted; reversal status=COMPLETED
And the POS had already received DE39=83 after the original timeout
```

### Scenario 2: POS-Initiated Reversal — Original Transaction Approved

```
Given a Sale with STAN=000257 is in pos_transaction (approved, bank TID=39360312)
When the POS terminal sends MTI 0400 with DE47={"origTrace":"000257"}
Then preprocessReversal() finds the record in pos_transaction
And handleReversalForApprovedTxn() creates a reversal request
And MTI 0400 is sent to the bank
And the bank responds DE39=00
And pos_transaction is moved to pos_failed_transaction with reversed=true
And the POS receives MTI 0410 with DE39=00
```

### Scenario 3: POS-Initiated Reversal — Already Reversed (Idempotent)

```
Given a reversal for STAN=000257 already has status=COMPLETED in pos_transaction_reversal
When the POS sends MTI 0400 again for the same STAN
Then preprocessReversal() detects COMPLETED status
And returns DE39=00 immediately without a bank call
```

### Scenario 4: POS-Initiated Reversal — Original Transaction Was Declined

```
Given the original Sale (STAN=000200) is in pos_failed_transaction (DE39=51)
When the POS sends MTI 0400 for STAN=000200
Then preprocessReversal() finds the failed transaction
And returns DE39=00 immediately (no funds were debited; no reversal needed)
```

### Scenario 5: Max Retries Exceeded

```
Given a reversal has been attempted 3 times and all received non-success responses
Then ReversalStatus is set to MAX_RETRIES_EXCEEDED
And TempTransactionStatus is set to PENDING_MANUAL_REVIEW
And alertOperations() logs a CRITICAL message
And no further automatic retries are scheduled
```

---

## 11. Business Requirements

### BR-REV-01 — Purpose
The system shall automatically initiate reversals to protect the cardholder from being charged for a transaction whose outcome is unknown due to a communication failure between the middleware and the bank acquirer.

### BR-REV-02 — Auto-Reversal Triggers
The system shall automatically initiate a reversal (MTI 0400) in any of the following conditions:
- Acquirer connection times out (`SocketTimeoutException`) during a financial transaction
- Acquirer returns a null response (connection lost)
- A database error occurs after the bank has approved but before the middleware can persist the result
- A stale `pos_temp_transaction` older than 45 seconds is detected on startup

### BR-REV-03 — POS-Initiated Reversal
The system shall also accept reversal requests (MTI 0400) sent directly by the POS terminal. The middleware shall determine the appropriate action based on the current state of the original transaction.

### BR-REV-04 — Original Transaction State Lookup
For POS-initiated reversals, the system shall check the following tables in order to determine the correct action:
1. `pos_transaction_reversal` — if already completed, return DE39=`00` immediately (idempotent)
2. `pos_failed_transaction` — if original was declined, return DE39=`00` (nothing to reverse)
3. `pos_temp_transaction` — if original is still in progress, trigger auto-reversal
4. `pos_transaction` — if original was approved, send reversal to the acquirer

### BR-REV-05 — Idempotency
If a reversal for the same original STAN and terminal has already been completed (`COMPLETED` status), the system shall return DE39=`00` to the POS terminal immediately without sending another MTI 0400 to the acquirer.

### BR-REV-06 — No Reversal for Already-Declined Transactions
If the original transaction exists in `pos_failed_transaction` (i.e., it was declined by the acquirer), the system shall return DE39=`00` without sending a reversal to the acquirer, as no funds were debited.

### BR-REV-07 — Reversal Message Construction
The system shall construct the MTI 0400 reversal request using data from the original transaction record, including the original STAN, processing code, amount, entry mode, PAN, expiry date, and RRN. DE-90 shall be populated with the original MTI, STAN, date, and time.

### BR-REV-08 — Immediate POS Response on Timeout
When an auto-reversal is triggered due to timeout, the system shall return DE39=`83` to the POS terminal immediately. The reversal to the acquirer shall proceed asynchronously and shall not block the POS terminal response.

### BR-REV-09 — Retry Logic
The system shall retry a failed reversal up to a maximum of 3 attempts. Each retry shall be scheduled 60 seconds after the previous failed attempt.

### BR-REV-10 — Success Criteria
A reversal is considered successful when the acquirer returns DE39=`00`, `21`, or `56`. All three codes shall result in `ReversalStatus.COMPLETED` and cleanup of the associated temp transaction.

### BR-REV-11 — Manual Review Escalation
If all 3 retry attempts fail, the system shall set the reversal status to `MANUAL_REVIEW` and log a CRITICAL alert. No further automatic retries shall be scheduled. Operations staff must resolve the discrepancy manually.

### BR-REV-12 — Atomic Cleanup
When a reversal succeeds, the system shall atomically delete the `pos_temp_transaction` record and update the `pos_transaction_reversal` status to `COMPLETED` in a single database transaction. Partial cleanup is not permitted.

### BR-REV-13 — Startup Orphan Cleanup
On application startup, the system shall identify and reverse all `pos_temp_transaction` records older than 5 minutes. These represent transactions that were in-flight when the middleware was last restarted.

### BR-REV-14 — No Receipt
The system shall not generate a receipt (DE-63) for any reversal message (MTI 0400/0410).

### BR-REV-15 — Processing Code Inheritance
The reversal message shall use the same processing code (DE-3) as the original transaction. A reversal of a Sale uses `000000`; a reversal of a Refund uses `200000`.
