# Feature: Void Transaction

**Transaction Type:** Void Sale
**Processor Class:** `VoidTransactionProcessor`
**Transaction Type Enum:** `TxnType.VOID_SALE`

---

## 1. Overview

A Void cancels a previously completed Sale (or Refund) transaction within the same settlement batch. The original transaction must exist in `pos_transaction` for a successful void. The POS terminal sends a financial request with DE3=`020000`, and the middleware sends a financial advice (MTI 0220) to the acquirer containing DE-90 (Original Data Elements) to identify the transaction being cancelled.

If the original transaction cannot be found by order number or STAN, the middleware returns DE39=`12` (Invalid Transaction) without contacting the bank.

Card data is not re-transmitted from the POS terminal — `VoidTransactionProcessor.onboardRequest()` copies the encrypted PAN, expiry date, and Track 2 from the original transaction in `pos_transaction`.

---

## 2. Flow Diagram

```
POS Terminal                   jPOS Middleware                        Bank Acquirer
     |                               |                                      |
     |--- MTI 0200 (DE3=020000) ---->|                                      |
     |    DE47={"origTrace":"000257"}|                                      |
     |                               |-- validateAndStoreRequest()          |
     |                               |-- preprocess() [VoidTransactionProcessor]
     |                               |   clearReversal() + clearTempTransaction()
     |                               |   lookup original txn by DE47.outOrderNo
     |                               |     OR by STAN + bank TID/MID in pos_transaction
     |                               |   if not found: return DE39=12 immediately
     |                               |-- onboardRequest()                   |
     |                               |   create pos_temp_transaction        |
     |                               |   copy encrypted PAN/expiry/track2   |
     |                               |     from matched pos_transaction      |
     |                               |-- evaluateRules() [HTTP]             |
     |                               |-- translator.toMessage()             |
     |                               |   getVoidMsg() -> MTI 0220 + DE90   |
     |                               |--- MTI 0220 (DE3=020000, DE90) ---->|
     |                               |<-- MTI 0230 (DE39=00) --------------|
     |                               |-- completeTransaction()              |
     |<-- MTI 0210 (DE39=00) --------|                                      |
```

---

## 3. ISO-8583 Message Specification

### 3.1 Request Fields (POS Terminal -> Middleware)

| DE # | Field Name                          | Format        | M/C | Description / Example                                              |
|------|-------------------------------------|---------------|-----|------------------------------------------------------------------- |
| MTI  | Message Type Indicator              | BCD F4        | M   | `0200`                                                             |
| 2    | PAN                                 | BCD LLVAR     | C   | Card PAN (may be present for card matching)                        |
| 3    | Processing Code                     | BCD F6        | M   | `020000` (Void Sale, default account)                              |
| 4    | Transaction Amount                  | BCD F12       | M   | `000000023008` = AED 230.08 (original sale amount)                 |
| 11   | STAN                                | BCD F6        | M   | `000035` (new STAN for this void request)                          |
| 12   | Local Transaction Time              | BCD F6        | M   | Current time (HHMMSS)                                              |
| 13   | Local Transaction Date              | BCD F4        | M   | Current date (MMDD)                                                |
| 41   | Card Acceptor Terminal ID (POS TID) | ASCII F8      | M   | `41448413`                                                         |
| 42   | Card Acceptor Merchant ID           | ASCII F15     | C   | POS merchant ID                                                    |
| 47   | Additional Data (JSON)              | ASCII LLLVAR  | M   | `{"origMti":"0200","origTrace":"000257","origDate":"20241128","origTime":"185628","outOrderNo":"ORD123"}` |
| 49   | Transaction Currency Code           | BCD F3        | M   | `784`                                                              |

> **DE-47 is critical:** `VoidTransactionProcessor.getOriginalInfo()` parses DE-47 as JSON (`AdditionalData`) to locate the original transaction. Lookup priority: `outOrderNo` first, then STAN + store + bank details.

### 3.2 Acquirer Request Fields (Middleware -> Acquirer, MTI 0220)

| DE # | Field Name                    | Format        | Description                                                                    |
|------|-------------------------------|---------------|--------------------------------------------------------------------------------|
| MTI  | Message Type Indicator        | BCD F4        | `0220` (`getVoidMsg()` forces this regardless of POS MTI)                      |
| 3    | Processing Code               | BCD F6        | `020000`                                                                       |
| 4    | Transaction Amount            | BCD F12       | Original sale amount                                                           |
| 11   | STAN                          | BCD F6        | Original transaction STAN (from `AdditionalData.originalStan`)                 |
| 12   | Local Transaction Time        | BCD F6        | Original transaction time (from `AdditionalData.originalTime`)                 |
| 13   | Local Transaction Date        | BCD F4        | Original transaction date, last 4 chars (from `AdditionalData.originalDate`)   |
| 19   | Acquiring Institution Country | BCD F3        | Set by middleware                                                               |
| 22   | POS Entry Mode                | BCD F3        | `011` = manual (void is always key-entry at acquirer)                          |
| 24   | NII                           | BCD F3        | Network International Identifier                                               |
| 25   | POS Condition Code            | BCD F2        | `00`                                                                           |
| 41   | Card Acceptor Terminal ID     | ASCII F8      | Bank TID (`39360312`)                                                          |
| 42   | Card Acceptor Merchant ID     | ASCII F15     | Bank MID (`000362511456113`)                                                   |
| 49   | Currency Code                 | BCD F3        | `784`                                                                          |
| 60   | Advice Reason Code            | ASCII LLLVAR  | Original sale amount (12 digits)                                               |
| 90   | Original Data Elements        | BCD F42       | `origMTI(4) + origSTAN(6) + origDate(4,MMDD) + origTime(6,HHMMSS) + 00000000000` |

> **DE-90 format example:** `0200` + `000257` + `0414` + `185628` + `00000000000`

### 3.3 Response Fields (Acquirer -> Middleware -> POS Terminal)

| DE # | Field Name               | Format       | M/C | Description / Example        |
|------|--------------------------|--------------|-----|------------------------------|
| MTI  | Message Type Indicator   | BCD F4       | M   | `0210` (POS response format) |
| 3    | Processing Code          | BCD F6       | M   | `020000` (echoed)            |
| 11   | STAN                     | BCD F6       | M   | POS STAN (restored)          |
| 37   | RRN                      | ASCII F12    | M   | From acquirer response       |
| 38   | Authorization Code       | ASCII F6     | C   | Present on approval          |
| 39   | Response Code            | ASCII F2     | M   | `00` = Void approved         |
| 41   | Card Acceptor Terminal ID| ASCII F8     | M   | POS TID (restored)           |
| 63   | Receipt JSON             | ASCII LLLVAR | C   | Receipt with type `"Void-Sale"` and `"PLEASE CREDIT MY ACCOUNT"` |

---

## 4. Processing Codes

| Processing Code | Meaning                     |
|-----------------|-----------------------------|
| `020000`        | Void Sale, default account  |
| `020100`        | Void Sale, savings account  |
| `020200`        | Void Sale, checking account |

---

## 5. Response Codes

| DE39 | Meaning                | Middleware Handling                                        |
|------|------------------------|------------------------------------------------------------|
| `00` | Approved               | Move temp -> `pos_transaction`                             |
| `12` | Invalid Transaction    | Returned immediately when original txn not found           |
| `76` | Terminal Mapping Error | DE41 not mapped in DB                                      |
| `81` | Terminal Busy          | Another transaction in progress for this terminal          |
| `83` | Acquirer Timeout       | `handleResponseTimeout()` -> MTI 0400 reversal             |
| `96` | System Malfunction     | Unhandled exception                                        |

---

## 6. Rules Engine

The rules engine is called identically to Sale. DE-42 and DE-41 sent are the **POS TID/MID** from the current request, not the bank TID/MID. If the rules engine declines (unusual), `cleanupTransaction()` removes the temp record and an error response is returned to the POS.

---

## 7. Receipt Generation (DE-63)

When `RULES_MODEL_NAME_ENABLED = true`, receipt type is `"Void-Sale"` (or `"Void-Sale Tip"` if DE-54 is present). The status block reads `"PLEASE CREDIT MY ACCOUNT"`.

---

## 8. Error Handling

| Scenario                                 | Behaviour                                                       |
|------------------------------------------|-----------------------------------------------------------------|
| DE-47 missing or unparseable             | Exception -> `MIDDLEWARE_SYSTEM_ERROR`; DE39=`78`               |
| Original txn not found by order no.      | `preprocessResponse` set with DE39=`12`; void rejected          |
| Original txn not found by STAN/TID/MID  | Same: DE39=`12` returned without bank call                      |
| Terminal busy                            | DE39=`81` returned from `preprocess()`                          |
| Acquirer timeout on void advice          | `handleResponseTimeout()` -> reversal for the void itself        |
| Database error                           | `handleDatabaseError()` -> DE39=`82`                            |

---

## 9. BDD Scenarios

### Scenario 1: Approved Void by Order Number

```
Given a prior Sale with orderNo="ORD123" exists in pos_transaction for terminal TID=41448413
And the rules engine returns ALLOW
When the terminal sends MTI 0200 with DE3=020000, DE4=000000023008
And DE47={"origMti":"0200","origTrace":"000035","origDate":"20260414","origTime":"182356","outOrderNo":"ORD123"}
Then VoidTransactionProcessor.preprocess() locates the original transaction by orderNo
And the middleware forwards MTI 0220 to the acquirer with DE90 containing original STAN and datetime
And the acquirer responds MTI 0230 with DE39=00
And the POS receives MTI 0210 with DE39=00
```

### Scenario 2: Void Rejected — Original Transaction Not Found

```
Given no transaction with STAN=000099 exists in pos_transaction for TID=41448413
When the terminal sends MTI 0200 with DE3=020000, DE47={"origTrace":"000099","outOrderNo":""}
Then preprocess() sets preprocessResponse with DE39=12
And no bank call is made
And the POS receives MTI 0210 with DE39=12 immediately
```

### Scenario 3: Terminal Busy During Void

```
Given AcquirerTerminalState.isBusy() returns true for terminal TID=41448413
When a Void request arrives
Then preprocess() returns false with errorCode=TERMINAL_BUSY
And the POS receives DE39=81
```

### Scenario 4: Void — Pending Reversal Cleared Before Processing

```
Given a pending PosTransactionReversal exists for terminal TID=41448413
When a Void request arrives
Then preprocess() calls clearReversal() which sends the pending MTI 0400
And after the reversal completes, the void proceeds normally
```

---

## 10. Business Requirements

### BR-VOID-01 — Purpose
The system shall support Void transactions that cancel a previously approved Sale within the same settlement batch, reversing the debit from the cardholder's account.

### BR-VOID-02 — Original Transaction Lookup
The system shall locate the original transaction using DE-47 (Additional Data JSON) before contacting the acquirer. Lookup shall follow this priority:
1. By `outOrderNo` (order number) if present and non-empty
2. By `origTrace` (STAN) combined with terminal and merchant identifiers

### BR-VOID-03 — Original Transaction Must Exist
If the original transaction cannot be found in `pos_transaction` by either lookup method, the system shall return DE39=`12` (Invalid Transaction) to the POS terminal immediately without contacting the acquirer.

### BR-VOID-04 — Same Batch Constraint
A Void shall only be permitted against transactions that exist in `pos_transaction` (approved and unsettled). Transactions that have already been settled or do not exist shall be rejected with DE39=`12`.

### BR-VOID-05 — Card Data Reuse
The system shall not require the POS terminal to re-transmit card data (PAN, expiry, Track 2) for a Void. The middleware shall retrieve the encrypted card data from the original transaction record in `pos_transaction` and use it in the acquirer request.

### BR-VOID-06 — DE-47 Mandatory
DE-47 (Additional Data) is mandatory for all Void requests. If DE-47 is absent or cannot be parsed as valid JSON, the system shall return DE39=`78` (Middleware System Error).

### BR-VOID-07 — Acquirer MTI Mapping
The system shall send MTI `0220` (Financial Advice) to the acquirer regardless of the MTI received from the POS terminal. The POS terminal receives MTI `0210` as the response.

### BR-VOID-08 — Original Data Elements (DE-90)
The system shall construct DE-90 for the acquirer request using the original transaction's MTI, STAN, date (MMDD), and time (HHMMSS) sourced from DE-47. This is the primary identifier used by the acquirer to locate and cancel the original transaction.

### BR-VOID-09 — Terminal Registration
The system shall reject any Void request where DE41 is not registered in the terminal database, returning DE39=`76` without contacting the acquirer.

### BR-VOID-10 — Terminal Busy Check
The system shall reject a Void request if the terminal already has an active transaction in progress, returning DE39=`81`.

### BR-VOID-11 — Pending Reversal Housekeeping
Before processing a Void, the system shall dispatch any pending reversals and clear any stale orphaned temp transactions for the requesting terminal.

### BR-VOID-12 — Rules Engine Evaluation
The system shall evaluate each Void through the rules engine using the POS TID/MID (not the bank TID/MID). If the rules engine returns `DECLINE`, the Void shall be rejected without a bank call. If the rules engine is unreachable, the system shall allow the Void (fail-mode: `OPEN`).

### BR-VOID-13 — TID/MID Mapping
The system shall replace the POS TID/MID with the bank-mapped TID/MID before forwarding to the acquirer. The original POS TID shall be restored in the response returned to the terminal.

### BR-VOID-14 — Transaction Persistence
The system shall create a `pos_temp_transaction` record before contacting the acquirer. On approval (DE39=`00`), it shall be promoted to `pos_transaction`. On decline, it shall be moved to `pos_failed_transaction`.

### BR-VOID-15 — Receipt Generation
When `model_name_enabled = true` is returned by the rules engine, the system shall generate a receipt in DE-63 of the response. The receipt type shall be `"Void-Sale"` and the status block shall read `"PLEASE CREDIT MY ACCOUNT"`.

### BR-VOID-16 — Acquirer Timeout
If the acquirer does not respond to the void advice within the configured timeout, the system shall initiate a reversal (MTI 0400) for the void request itself, following the same retry logic as Sale reversals (max 3 retries, 60-second intervals).

### BR-VOID-17 — Success Criteria
A Void is considered approved when the acquirer returns DE39=`00`. All other response codes are treated as declined.
