# Receipt Handling

This document covers how the host conditionally delivers receipt data, how the app parses it, and how each receipt mode (cloud, local, declined, NFC, TOMS) is selected and rendered.

---

## 1. High-Level Flow

```
ISO 8583 Response received
  └─ Packet8583.parseRespons()          — parse fields 2,4,12,13,37,38,39,55,60 → PubBean
  └─ Caller.extractResponseDataCloud()  — parse Field 63 JSON → PubBean.responseDataCloud

Response code classification (ResultCode)
  ├─ Approved (00 / 10)   → AddRecordStep → PrintReceiptStep → NFCReceiptStep → FlyReceiptStep
  ├─ Server Declined       → DeclinedReceiptStep (prints, then STOPS chain)
  └─ Local failure (UC/FL) → Error dialog, no receipt
```

---

## 2. ISO 8583 Field Parsing — `Packet8583` & `Caller`

### Standard Fields → PubBean

`core/.../pack/iso/Packet8583.java` — `parseRespons()` maps response fields:

| Field | PubBean setter | Content |
|-------|---------------|---------|
| 2 | `setCardNo()` | Masked card number |
| 4 | `setAmount()` | Transaction amount |
| 12 | `setTime()` | Time `HHmmss` |
| 13 | `setDate()` | Date `MMDD` or `yyyyMMdd` |
| 37 | `setReferNo()` | Retrieval Reference Number |
| 38 | `setAuthCode()` | Authorisation code |
| 39 | `setResultCode()` + `setMessage()` | Response code — **drives all receipt logic** |
| 55 | `setRespField55()` | EMV cryptogram data |
| 60 | `setBankDetails()` | Middleware reference (bank-provided MID/TID/date/trace) |

### Field 63 — Cloud Receipt Data

`core/.../pack/iso/Caller.java` — `extractResponseDataCloud()` runs after field parsing:

```
field63 = iso8583.getField(63)
  │
  ├─ Empty / absent → skip (no cloud receipt this transaction)
  │
  └─ Present:
       ├─ Hex-encoded? → decode bytes → UTF-8 string
       ├─ Strip any TLV prefix bytes before first '{'
       ├─ Extract substring from '{' to '}'
       ├─ Parse as JSONObject
       └─ Contains "receipt" or "r" key?
            ├─ YES → pubBean.setResponseDataCloud(json.toString())
            └─ NO  → discard (Field 63 used for other purpose, e.g. KSN)
```

**Field 63 is dual-purpose.** For DUKPT, the KSN is packed into field 63 on the *request*. On the *response*, the host may return cloud receipt JSON in field 63. `extractResponseDataCloud` only stores the value when the field contains a JSON object with a `"receipt"` or `"r"` key.

### Field 63 JSON Shape

```json
{
  "receipt": {
    "elements": [
      { "txt": { "va": "APPROVED",   "al": "CENTER", "b": 1, "fs": "2:2" } },
      { "fld": { "lb": "Batch:",     "va": "000001" } },
      { "fld": { "lb": "Trace:",     "va": "000123" } },
      { "lg":  { "url": "https://example.com/logo.png" } },
      { "dualField": { "leftLabel": "Amount", "leftValue": "100.00",
                       "rightLabel": "Tip",   "rightValue": "5.00" } },
      { "emv": {} },
      { "ls": {} },
      { "pc": {} }
    ]
  }
}
```

Short-form aliases: `"r"` for `"receipt"`, `"e"` for `"elements"`.
Backward-compatible: if the field value is a bare JSON array (no wrapper object), it is treated as the elements array directly.

#### Element Reference

| Key | Fields | Purpose |
|-----|--------|---------|
| `"txt"` | `va` (text), `al` (LEFT/CENTER/RIGHT), `b` (1=bold), `fs` ("X:Y" scale) | Free text line |
| `"fld"` | `lb` (label), `va` (value) | Label : value row |
| `"dualField"` | `leftLabel`, `leftValue`, `rightLabel`, `rightValue` | Two-column row |
| `"lg"` | `url` | Download and render logo image |
| `"flg"` | `url` | Download and render footer logo |
| `"emv"` | — | Render EMV block (AID, TVR, CID, TSI, AC) from `record.emvPrintData` |
| `"ls"` | — | Blank line separator |
| `"pc"` | — | Paper feed / cut |

**Font size mapping for `"txt"` elements** (`fs` field = `"X:Y"` where X=Y=scale factor):

| Scale | Bold? | Result |
|-------|-------|--------|
| ≥ 3.0 | any | AMOUNT (largest) |
| ≥ 2.0 | yes | AMOUNT |
| ≥ 2.0 | no | TRAN_TYPE |
| ≥ 1.0 | yes | TRAN_TYPE |
| ≥ 1.0 | no | NORMAL |
| > 0 | any | SMALL_PROPORTIONAL |

---

## 3. Response Code Classification — `ResultCode`

`core/.../constant/ResultCode.java` classifies every response code before any receipt decision is made:

| Classification | Codes | Meaning |
|---------------|-------|---------|
| **Approved** | `"00"`, `"10"` | Transaction succeeded |
| **Custom / Local** | `"UC"` (user cancel), `"FL"` (system failure) | Not from host |
| **Server Declined** | everything else | Host explicitly declined |

```java
ResultCode.isApproved(code)      // "00" or "10"
ResultCode.isCustomCode(code)    // "UC" or "FL"
ResultCode.isAcquirerDeclined(code)  // !approved && !custom
```

---

## 4. Conditional Step Chain

The sale step chain (and all other transaction types) is composed to handle all three outcomes without branching in the transaction class itself:

```
PreCheckStep
InputAmountStep
TipAmountStep
ReadCardStep ──► PackSaleStep ──► Caller.execute()
                                       │
                          ┌────────────┴────────────┐
                          ▼                         ▼
                     OK returned              FAIL returned
                     (approved OR             (UC / FL /
                      server declined)         comms error)
                          │                         │
                          ▼                         ▼
               DeclinedReceiptStep           Error dialog
               (skips if approved)           (no receipt)
               (prints + STOPS if declined)
                          │ (only reaches here if approved)
                          ▼
               AddRecordStep
               (persists to Room DB)
                          │
                          ▼
               SignatureStep
                          │
                          ▼
               PrintReceiptStep
               (skipped if NFC mode or no printer)
                          │
                          ▼
               NFCReceiptStep
               (runs only if PARAMS_KEY_NFC_RECEIPT = true)
                          │
                          ▼
               FlyReceiptStep
               (runs only if PARAMS_KEY_TOMS_FLY_RECEIPT = true)
```

---

## 5. Approved Transaction — `PrintReceiptStep`

`core/.../steps/PrintReceiptStep.java`

### Skip Conditions (checked in order)

1. `PARAMS_KEY_NFC_RECEIPT == true` → skip (NFCReceiptStep handles delivery)
2. `!BDevice.supportPrint() && !PARAMS_KEY_PRINT_EXTERNAL` → skip (no printer available)

### Receipt Data Priority

```java
// 1. Reprint path: cloud data already saved in database record
if (record != null && !TextUtils.isEmpty(record.getResponseDataCloud())) {
    cloudReceiptData = record.getResponseDataCloud();
}
// 2. New transaction: cloud data extracted from Field 63 this transaction
else if (!TextUtils.isEmpty(pubBean.getResponseDataCloud())) {
    cloudReceiptData = pubBean.getResponseDataCloud();
}
// 3. No cloud data: fall through to local template
```

`cloudReceiptData` (or `null`) is forwarded to `PrintFragment` → `PrintViewModel.getReceipt()`.

### `PrintViewModel.getReceipt()`

```
cloudReceiptData present?
  ├─ YES → generateCloudReceipt(record, cloudReceiptData, index, isReprint)
  │         Renders each element from the JSON array onto a Bitmap
  │         Falls back to local receipt on parse error
  └─ NO  → Local receipt template
              Uses BankDetailsParser to extract Field 60 values:
              bankMid, bankTid, bankDate, bankTime, bankBatch, bankStan, bankRefNum
              Bank-provided values override terminal values on the printed slip
```

**Print copies:** Controlled by `PARAMS_KEY_PRINT_COUNT`. `PrintReceiptStep` loops and calls the print method for each copy.

---

## 6. Declined Transaction — `DeclinedReceiptStep`

`core/.../steps/DeclinedReceiptStep.java`

This step runs **before** `AddRecordStep`. Its purpose is to print a declined slip and halt the chain so no database record is created.

### Run Condition

```java
// Skip if approved OR local failure — only act on server decline codes
if (isEmpty(responseCode)
        || ResultCode.isApproved(responseCode)
        || ResultCode.isCustomCode(responseCode)) {
    callback.onResult(true);  // pass-through
    return;
}
```

### Declined Receipt Logic

```
Build temporary in-memory Record (NOT saved to DB)
  │
  ├─ pubBean.getResponseDataCloud() present?
  │    ├─ YES → PrintViewModel.getReceipt(tempRecord, false, 0, cloudData)
  │    │         Host provided a styled declined receipt via Field 63 — render it
  │    └─ NO  → PrintViewModel.getDeclinedReceipt(tempRecord)
  │              Local declined receipt template (generic "Declined" layout)
  │
  └─ printer.print(bitmap)
       └─ callback.onResult(false)   ← STOPS the step chain
```

**Key behaviour:** A declined transaction may still receive a cloud receipt from the host via Field 63. When present, that receipt is used even for declined slips — the host controls the exact wording and layout.

---

## 7. Field 60 — Bank-Provided Receipt Values

`core/.../utils/BankDetailsParser.java`

Field 60 (`bankDetails` / `middlewareReference`) carries bank-assigned values that override terminal-derived values on the printed receipt:

| Extracted Value | Used on Receipt As |
|-----------------|--------------------|
| `getBankMerchantId()` | MID line |
| `getBankTerminalId()` | TID line |
| `getBankTxnDate()` | Transaction date |
| `getBankTxnTime()` | Transaction time |
| `getBankBatchNumber()` | Batch number |
| `getBankStan()` | STAN / trace |
| `getBankTxnRefNumber()` | Reference number |

These are only used in the **local receipt template** path. Cloud receipt (`generateCloudReceipt`) renders whatever text the host supplies in the Field 63 JSON — it does not additionally read Field 60.

---

## 8. Database Persistence — `AddRecordStep` & `Record`

`AddRecordStep` only runs for **approved** transactions (response code `"00"` or `"10"`). Declined transactions produce a temporary `Record` in `DeclinedReceiptStep` that is never written to the database.

`DataConverter.pubBeanToRecord()` copies the full PubBean snapshot to a `Record`:

| Record Column | Source |
|--------------|--------|
| `RESPONSE_CODE` | `pubBean.getResultCode()` (Field 39) |
| `REFER_NO` | Field 37 |
| `AUTH_CODE` | Field 38 |
| `FIELD_55` | Field 55 (EMV data) |
| `BANK_DETAILS` | Field 60 |
| `RESPONSE_DATA_CLOUD` | Field 63 JSON (cloud receipt) |
| `MW_REFERENCE` | Field 60 (duplicate — used by BankDetailsParser) |
| `EMV_PRINT_DATA` | Formatted EMV block for printing |
| `SIGN_PATH` | Signature bitmap file path |

The `RESPONSE_DATA_CLOUD` column is what enables **reprint** to render identically to the original — the cloud JSON from the host is preserved verbatim.

---

## 9. NFC Receipt — `NFCReceiptStep`

`core/.../steps/NFCReceiptStep.java`

### Run Condition
`PARAMS_KEY_NFC_RECEIPT == true` AND a database `record` exists.

When active, `PrintReceiptStep` is skipped (no paper slip printed).

### Behaviour
```
BNtagCardProcessor.open()
  └─ Set receipt parameters:
       ├─ Terminal ID (from record)
       └─ RRN (from record)
  └─ Begin NFC emulation
  └─ Auto-close after 9 seconds
  └─ callback.onResult(true)  ← chain continues regardless
```

Receipt content delivered via NFC is assembled by the NSDK NTag layer — the app provides the terminal/transaction identifiers; the NSDK handles the NFC NDEF encoding.

---

## 10. TOMS Fly Receipt — `FlyReceiptStep`

`core/.../steps/FlyReceiptStep.java`

### Run Condition
`PARAMS_KEY_TOMS_FLY_RECEIPT == true` AND a database `record` exists.

### Behaviour
```
PrintViewModel.getReceipt(record, false, 0, cloudData)
  └─ Renders receipt Bitmap (cloud or local — same priority as PrintReceiptStep)

FlyReceiptHelper.getInstance().sendReceipt(context, bitmap, vouchBean)
  └─ Builds VouchUpRequestBean with:
       ├─ Merchant / Terminal IDs
       ├─ Transaction amount, type, date, RRN, auth code
       └─ Receipt bitmap (encoded)
  └─ TOMSClientMssApiManager → async HTTP upload to TOMS platform
  └─ callback.onResult(true)  ← chain continues regardless of upload result
```

The TOMS upload is fire-and-forget — failure does not affect the transaction result or print flow.

---

## 11. Receipt Rendering Engine — `PrintViewModel.generateCloudReceipt()`

`core/.../fragment/print/PrintViewModel.java`

The cloud receipt renderer iterates the `elements` array from Field 63 JSON and draws each element onto an Android `Bitmap` using `BitmapDraw`:

```
For each element object in elements[]:
  ├─ "ls"        → addLineSeparator()
  ├─ "pc"        → addPaperCut()
  ├─ "lg"        → downloadImage(url, 5s timeout, SSL bypassed)
  │                 fallback: device default logo
  ├─ "flg"       → downloadImage(url) as footer
  ├─ "txt"       → addText(va, alignment, bold, fontSize)
  ├─ "fld"       → addField(lb, va)
  ├─ "dualField" → addDualField(leftLabel, leftValue, rightLabel, rightValue)
  └─ "emv"       → addEmvBlock(record.emvPrintData)
                     renders: AID, TVR, CID, TSI, AC
```

Logo images are downloaded at render time with SSL verification disabled (intended for internal/private image servers). If download fails, the device's default logo is substituted.

---

## 12. Receipt Mode Decision Matrix

| Scenario | Field 63 JSON? | `CLOUD_RECEIPT` param | `NFC_RECEIPT` param | Result |
|----------|:--------------:|:--------------------:|:-------------------:|--------|
| Approved, host sends Field 63 | ✓ | any | false | Cloud receipt printed (Field 63 content) |
| Approved, no Field 63 | ✗ | any | false | Local receipt template (Field 60 values) |
| Approved, NFC mode | any | any | true | No print; NFC emulation for 9 s |
| Server declined, host sends Field 63 | ✓ | any | false | Host-styled declined slip printed; chain stops |
| Server declined, no Field 63 | ✗ | any | false | Generic declined template; chain stops |
| Local failure (UC / FL) | — | — | — | No receipt; error dialog only |
| TOMS Fly Receipt enabled | any | any | any | After print/NFC: receipt bitmap uploaded to TOMS |

---

## 13. Relevant Source Files

| File | Role |
|------|------|
| `core/.../pack/iso/Caller.java` | Host comms, response parsing, Field 63 extraction |
| `core/.../pack/iso/Packet8583.java` | ISO 8583 field-by-field parsing into PubBean |
| `core/.../bean/PubBean.java` | In-memory transaction data (incl. `responseDataCloud`) |
| `core/.../constant/ResultCode.java` | Response code classification |
| `core/.../steps/DeclinedReceiptStep.java` | Declined slip print + chain halt |
| `core/.../steps/AddRecordStep.java` | Approved-only DB persistence |
| `core/.../steps/PrintReceiptStep.java` | Paper receipt print (skip logic + data priority) |
| `core/.../steps/NFCReceiptStep.java` | NFC receipt emulation |
| `core/.../steps/FlyReceiptStep.java` | TOMS receipt upload |
| `core/.../fragment/print/PrintViewModel.java` | Bitmap rendering (cloud + local templates) |
| `core/.../utils/BankDetailsParser.java` | Field 60 value extraction for local receipts |
| `core/.../tools/DataConverter.java` | PubBean → Record mapping |
| `database/.../model/Record.java` | Persisted receipt fields (incl. `RESPONSE_DATA_CLOUD`) |
| `sdk_helper/.../FlyReceiptHelper.java` | TOMS platform upload |
