# Declined Transaction Receipt Generation - Implementation Documentation

**Date:** December 5, 2025  
**Branch:** feature/reciept_generation_for_errCodes  
**Purpose:** Generate and print minimal receipts for declined transactions without saving to database

---

## Executive Summary

This document explains the complete implementation for printing minimal "VOID" receipts when transactions are declined by the server. The system distinguishes between:
- **Approved transactions** (code "00"): Full detailed receipts, saved to database
- **Declined transactions** (codes "01"-"95"): Minimal VOID receipts, NOT saved to database
- **Failed transactions** (no server response): No receipt, no database save

---

## Table of Contents

1. [Problem Statement](#problem-statement)
2. [Solution Overview](#solution-overview)
3. [Detailed Flow Explanation](#detailed-flow-explanation)
4. [Code Changes Breakdown](#code-changes-breakdown)
5. [Why ReadCardStep Check is Critical](#why-readcardstep-check-is-critical)
6. [Files Modified](#files-modified)
7. [Implementation Timeline](#implementation-timeline)

---

## Problem Statement

### Original Issue
When a transaction was declined by the server, the application would:
1. Display the decline message on screen
2. NOT print any receipt
3. NOT save to database (correct)

**User requirement:** Print a minimal "VOID" receipt showing:
- Transaction type
- Date/Time
- Amount
- Decline reason (error code + message)

### Technical Challenge: EMV + Server Decline Scenario

The complexity arises with chip card (EMV) transactions that are declined by the server:

```
Timeline of Events:
1. Card read successfully (chip card detected)
2. PIN entered
3. EMV 1st GAC (Generate Application Cryptogram) succeeds
4. Transaction packed (ISO8583 message created)
5. Caller sends message to server
6. Server processes transaction
7. Server RESPONDS WITH "01" (Declined - Refer to Card Issuer)
8. Response code "01" is extracted and stored in pubBean.setResultCode("01")
9. EMV 2nd GAC is attempted
10. EMV 2nd GAC FAILS with error -8021
    (Reason: Card rejects the declined response cryptogram)
11. CardFragment.onFail() is called
12. ReadCardStep.onFail() callback is triggered
```

**The Problem:** At step 11-12, the chain would break because:
- EMV processing failed (returned onFail)
- Normal logic: onFail = break chain, no receipt
- BUT: We have a valid server response code "01" that was already received!

**This is the KEY INSIGHT** that drives the entire implementation.

---

## Solution Overview

### Three-Part Solution

#### Part 1: Response Code Mapping (answercode.properties)
Map all ISO8583 Field 39 response codes to user-friendly messages:
```
01=(01) REFERRED TO CARD ISSUER
05=(05) DO NOT HONOR
51=(51) INSUFFICIENT FUNDS
...etc for 35+ error codes
```

#### Part 2: Smart Chain Decision Logic (ReadCardStep.java)
At the point where EMV fails but server response exists, make an intelligent decision:
- **Has server response code?** → Continue chain to print receipt
- **No server response?** → Stop chain (actual failure)

#### Part 3: Minimal Receipt Generation (PrintViewModel + DeclinedReceiptStep)
- Generate minimal bitmap with only essential info
- Print immediately in DeclinedReceiptStep
- Stop chain after printing (prevent duplicate receipts)

---

## Detailed Flow Explanation

### For Approved Transactions (Response Code "00")

```
1. Sale.java starts chain
   ↓
2. ReadCardStep.intercept()
   - CardFragment displays card entry screen
   - Card read successful
   - PIN entered
   - EMV 1st GAC succeeds
   - PackSaleStep executes → Caller sends to server
   - Server responds "00" (Approved)
   - pubBean.setResultCode("00")
   - EMV 2nd GAC succeeds
   - CardFragment.onSuccess() called
   ↓
3. ReadCardStep.onSuccess()
   → callback.onResult(true) - Continue chain
   ↓
4. DeclinedReceiptStep.intercept()
   - Checks pubBean.getResultCode() = "00"
   - Condition: ResultCode.OK.equals("00") → TRUE
   - Skips this step (not a declined transaction)
   → callback.onResult(true) - Continue chain
   ↓
5. AddRecordStep.intercept()
   - Saves transaction to database
   → callback.onResult(true) - Continue chain
   ↓
6. SignatureStep → PrintReceiptStep
   - Prints full detailed receipt
   - Includes all transaction details + EMV data
   ↓
7. NFCReceiptStep → FlyReceiptStep
   ↓
8. Transaction complete
```

### For Declined Transactions (Response Code "01"-"95")

```
1. Sale.java starts chain
   ↓
2. ReadCardStep.intercept()
   - CardFragment displays card entry screen
   - Card read successful
   - PIN entered
   - EMV 1st GAC succeeds
   - PackSaleStep executes → Caller sends to server
   - Server responds "01" (Declined)
   - pubBean.setResultCode("01")
   - EMV 2nd GAC FAILS with -8021 (chip rejects decline)
   - CardFragment.onFail() called
   ↓
3. ReadCardStep.onFail(errorType, errorMsg)
   *** THIS IS THE CRITICAL DECISION POINT ***
   
   Check: if (responseCode != null 
           && !isEmpty 
           && !isCustomCode 
           && !ResultCode.OK.equals(responseCode))
   
   Result: responseCode = "01" matches!
   Action: callback.onResult(true) - CONTINUE chain
           (This is the fix that allows receipt printing!)
   ↓
4. DeclinedReceiptStep.intercept()
   - Checks pubBean.getResultCode() = "01"
   - Condition: ResultCode.OK.equals("01") → FALSE
   - Condition: ResultCode.isCustomCode("01") → FALSE
   - This IS a declined transaction!
   
   Actions:
   a) Create temporary Record object (not saved to DB)
   b) Populate from pubBean via DataConverter
   c) Call PrintViewModel.getDeclinedReceipt(tempRecord)
   d) Get minimal receipt bitmap
   e) Printer.print(bitmap)
   f) When print finishes: callback.onResult(false)
      *** STOPS THE CHAIN ***
   ↓
5. Chain stops here - AddRecordStep NOT executed
   - No database save
   - PrintReceiptStep NOT executed
   - Only minimal VOID receipt printed
   ↓
6. Transaction complete
```

### For Failed Transactions (No Server Response)

```
1. Sale.java starts chain
   ↓
2. ReadCardStep.intercept()
   - CardFragment displays card entry screen
   - Card read FAILS (e.g., network error, card not readable)
   - pubBean.getResultCode() = null or "UC" or "FL"
   - CardFragment.onFail() called
   ↓
3. ReadCardStep.onFail(errorType, errorMsg)
   
   Check: if (responseCode != null 
           && !isEmpty 
           && !isCustomCode 
           && !ResultCode.OK.equals(responseCode))
   
   Result: responseCode = null or "UC" or "FL"
   - responseCode == null? → FALSE - doesn't match
   - isCustomCode("UC")? → TRUE - matches custom code check
   
   Action: callback.onResult(false) - BREAK CHAIN
           (No receipt, no database save)
   ↓
4. Chain stops - DeclinedReceiptStep NOT executed
5. No receipt printed
6. Transaction complete with error
```

---

## Code Changes Breakdown

### 1. answercode.properties (35+ error codes added)

**Location:** `core/src/main/assets/answercode.properties`

**Content:**
```properties
00=Approved
01=(01) REFERRED TO CARD ISSUER
05=(05) DO NOT HONOR
51=(51) INSUFFICIENT FUNDS
... (35+ more codes)
95=(95) SYSTEM MALFUNCTION
```

**Purpose:** Maps ISO8583 Field 39 response codes to POS display messages

**Used by:**
- `AnswerCodeProvider.getRspMessage(String responseCode)` → Returns formatted message
- `Packet8583.parseResponse()` → Populates pubBean message field
- `PrintViewModel.getDeclinedReceipt()` → Displays message on receipt

---

### 2. ReadCardStep.java (CRITICAL - Declined Transaction Detection)

**Location:** `core/src/main/java/acquire/core/trans/steps/ReadCardStep.java`

**Lines Modified:** 110-120 (onFail callback)

**Original Code:**
```java
@Override
public void onFail(int errorType, String errorMsg) {
    callback.onResult(false);  // Always breaks chain
}
```

**New Code:**
```java
@Override
public void onFail(int errorType, String errorMsg) {
    // Check if we have a server response code (declined transaction)
    String responseCode = pubBean.getResultCode();
    if (responseCode != null && !responseCode.isEmpty() 
            && !ResultCode.isCustomCode(responseCode) 
            && !ResultCode.OK.equals(responseCode)) {
        // This is a declined transaction with a valid server response
        // Continue the chain to allow receipt printing
        LoggerUtils.d("ReadCardStep: Declined transaction with response code: " 
                     + responseCode + ", continuing chain for receipt");
        callback.onResult(true);  // ← CONTINUE chain for receipt
    } else {
        // This is an actual failure (no server response or other error)
        callback.onResult(false);  // Break chain
    }
}
```

**Why This is Critical:**

The `onFail()` method is called when CardFragment encounters an error. **Without this check**, the chain would always break, preventing receipt printing for declined transactions.

**The Check Logic:**
1. `responseCode != null` → Did we get a response code?
2. `!responseCode.isEmpty()` → Is it not empty?
3. `!ResultCode.isCustomCode(responseCode)` → Is it a server code (not UC/FL)?
4. `!ResultCode.OK.equals(responseCode)` → Is it not "00" (not approved)?

**If ALL conditions true:** Server declined the transaction → Continue chain
**If ANY condition false:** Actual failure (no server response) → Stop chain

---

### 3. DeclinedReceiptStep.java (New File - Handles Declined Receipts)

**Location:** `core/src/main/java/acquire/core/trans/steps/DeclinedReceiptStep.java`

**Purpose:** Generate and print minimal receipts for declined transactions

**Key Logic:**
```java
public void intercept(Callback callback) {
    String responseCode = pubBean.getResultCode();
    
    // Only process declined transactions
    if (isEmpty(responseCode) || OK.equals(responseCode) || isCustomCode(responseCode)) {
        callback.onResult(true);  // Skip for approved/failed
        return;
    }
    
    // This is declined - generate minimal receipt
    Record tempRecord = new Record();
    DataConverter.pubBeanToRecord(pubBean, tempRecord);
    
    // Generate minimal bitmap
    Bitmap declinedReceiptBitmap = PrintViewModel.getDeclinedReceipt(tempRecord);
    
    // Print it
    IPrinter printer = new BPrinter();
    printer.print(declinedReceiptBitmap, new IPrinter.PrintCallback() {
        @Override
        public void onFinish() {
            callback.onResult(false);  // ← STOPS chain
        }
        
        @Override
        public void onError(String message) {
            callback.onResult(false);  // Stop chain even if print fails
        }
    });
}
```

**Design Decisions:**
- Creates temporary Record (never saved to DB)
- Calls `PrintViewModel.getDeclinedReceipt()` for minimal bitmap
- Prints immediately (synchronous decision point)
- Returns `false` to stop chain (prevents duplicate receipts)

---

### 4. PrintViewModel.java (New Method - Minimal Receipt Template)

**Location:** `core/src/main/java/acquire/core/fragment/print/PrintViewModel.java`

**New Method:** `getDeclinedReceipt(Record record)`

**Minimal Receipt Layout:**
```
[Logo Image]

VOID

[TRANSACTION TYPE]
[DATE/TIME]
[AMOUNT]
[ERROR CODE & MESSAGE]

Thank You

-----x-----x-----
[Paper feed]
```

**Implementation:**
```java
public static @NonNull Bitmap getDeclinedReceipt(Record record) {
    BitmapDraw bitmapDraw = new BitmapDraw();
    
    // Logo
    bitmapDraw.image(BitmapFactory.decodeStream(...));
    
    // VOID header
    bitmapDraw.text("VOID", PrintSize.NORMAL, true, Paint.Align.CENTER);
    bitmapDraw.feedPaper(20);
    
    // Transaction type
    bitmapDraw.text(TransUtils.getName(record.getTransType()), 
                    PrintSize.TRAN_TYPE, true, Paint.Align.CENTER);
    
    // Date and Time
    bitmapDraw.text("DATE/TIME", 
                    FormatUtils.formatTimeStamp(record.getDate() + record.getTime()), 
                    PrintSize.NORMAL, false);
    
    // Amount
    bitmapDraw.text(CurrencyCodeProvider.getCurrencySymbol(record.getCurrencyCode()) 
                    + FormatUtils.formatAmount(record.getAmount()), 
                    PrintSize.AMOUNT, true, Paint.Align.CENTER);
    
    // Error message
    String responseMsg = AnswerCodeProvider.getRspMessage(record.getResponseCode());
    if (!TextUtils.isEmpty(responseMsg)) {
        bitmapDraw.text(responseMsg, PrintSize.NORMAL, true, Paint.Align.CENTER);
    }
    
    bitmapDraw.feedPaper(20);
    bitmapDraw.text("Thank You", PrintSize.NORMAL, false, Paint.Align.CENTER);
    bitmapDraw.text("-----x-----x-----", PrintSize.LINE, false, Paint.Align.CENTER);
    bitmapDraw.feedPaper(PrintSize.END_FEED);
    
    return bitmapDraw.getBitmap();
}
```

**Compared to Full Receipt:**
- Full receipt: ~50 lines of code, includes merchant details, card info, EMV data, signature panel
- Minimal receipt: ~15 lines of code, only essential info

---

### 5. Sale.java and Other Transaction Files (Chain Placement)

**Locations:**
- `core/src/main/java/acquire/core/trans/impl/sale/Sale.java`
- `core/src/main/java/acquire/core/trans/impl/refund/Refund.java`
- `core/src/main/java/acquire/core/trans/impl/voidsale/VoidSale.java`
- `core/src/main/java/acquire/core/trans/impl/voidpreauth/VoidPreAuth.java`
- `core/src/main/java/acquire/core/trans/impl/voidinstallment/VoidInstallment.java`
- `core/src/main/java/acquire/core/trans/impl/voidauthcomplete/VoidAuthComplete.java`
- `core/src/main/java/acquire/core/trans/impl/preauth/PreAuth.java`
- `core/src/main/java/acquire/core/trans/impl/installment/Installment.java`

**Change Pattern:**
```java
// Before
chain.next(new ReadCardStep(...))
     .next(new AddRecordStep())
     .next(new PrintReceiptStep())

// After
chain.next(new ReadCardStep(...))
     .next(new DeclinedReceiptStep())  // ← Added
     .next(new AddRecordStep())
     .next(new PrintReceiptStep())
```

**Why This Placement:**
- After ReadCardStep: Ensures card processing is complete
- Before AddRecordStep: Intercepts before database operations
- Executed for ALL transaction types: Consistent declined receipt printing

---

## Why ReadCardStep Check is Critical

### The Problem It Solves

Without the ReadCardStep check, consider this scenario:

**Scenario: Chip card declined by server**

```
Step 1: CardFragment processes chip card
  - Read card: ✓
  - Verify PIN: ✓
  - 1st GAC: ✓
  - Pack & Send to Server: ✓
  - Server Response: "01" (Declined)
  - pubBean.setResultCode("01") ✓

Step 2: EMV 2nd GAC attempted
  - 2nd GAC FAILS: -8021
  - CardFragment.onFail() called

Step 3: WITHOUT the check (old behavior)
  - ReadCardStep.onFail() says "something failed"
  - callback.onResult(false)
  - Chain breaks immediately
  - DeclinedReceiptStep never executes
  - No receipt printed
  - User leaves unhappy

Step 4: WITH the check (new behavior)
  - ReadCardStep.onFail() checks pubBean.getResultCode()
  - Finds "01" (valid server response)
  - Understands: "This isn't a failure, it's a decline!"
  - callback.onResult(true)
  - Chain continues to DeclinedReceiptStep
  - Minimal receipt printed
  - User leaves satisfied
```

### Why This Check Can't Be Elsewhere

**Option 1: Put in Caller.java**
- ❌ Caller doesn't know about DeclinedReceiptStep
- ❌ Caller doesn't control the chain
- ❌ Caller returns CallerResult, not chain control

**Option 2: Put in DeclinedReceiptStep**
- ❌ Only executes if ReadCardStep returns true
- ❌ Passive receiver, not active decision maker
- ❌ Chain already broken before it runs

**Option 3: Put in Sale.java**
- ❌ Chain file shouldn't have business logic
- ❌ Doesn't know about ResponseCode
- ❌ Just a configuration file

**Option 4: Put in ReadCardStep ✓**
- ✓ Sees both EMV error AND server response
- ✓ Controls chain continuation decision
- ✓ Only place with complete context
- ✓ Most logical location in execution flow

### The Unique Visibility of ReadCardStep

Only ReadCardStep can see BOTH:

```
Event 1: Server sent response code "01"
         (Available in: pubBean.getResultCode())
         (Set by: Caller.java → Packet8583.parseResponse())

Event 2: EMV 2nd GAC failed with -8021
         (Triggers: CardFragment.onFail())
         (Received by: ReadCardStep.onFail())
```

**Only ReadCardStep receives both pieces of information in the same callback!**

This is why it's the ONLY place where this decision can be made intelligently.

---

## Files Modified

| File | Change Type | Reason |
|------|-------------|--------|
| `answercode.properties` | Added | Map 35+ error codes to messages |
| `ReadCardStep.java` | Modified | Add declined transaction detection |
| `DeclinedReceiptStep.java` | Created | Generate & print minimal receipts |
| `PrintViewModel.java` | Modified | Add `getDeclinedReceipt()` method |
| `Sale.java` | Modified | Add DeclinedReceiptStep to chain |
| `Refund.java` | Modified | Add DeclinedReceiptStep to chain |
| `VoidSale.java` | Modified | Add DeclinedReceiptStep to chain |
| `VoidPreAuth.java` | Modified | Add DeclinedReceiptStep to chain |
| `VoidInstallment.java` | Modified | Add DeclinedReceiptStep to chain |
| `VoidAuthComplete.java` | Modified | Add DeclinedReceiptStep to chain |
| `PreAuth.java` | Modified | Add DeclinedReceiptStep to chain |
| `Installment.java` | Modified | Add DeclinedReceiptStep to chain |

---

## Implementation Timeline

### Phase 1: Response Code Mapping
- Added all 35+ ISO8583 Field 39 response codes to `answercode.properties`
- Formatted as: `CODE=(CODE) MESSAGE`
- Example: `01=(01) REFERRED TO CARD ISSUER`

### Phase 2: Chain Decision Logic
- Identified EMV + Decline edge case in ReadCardStep
- Added responseCode check in `onFail()` callback
- Implemented logic to continue chain for declined, break for failures

### Phase 3: Receipt Generation
- Created `getDeclinedReceipt()` method in PrintViewModel
- Designed minimal receipt template with essential info only
- Created DeclinedReceiptStep to handle printing

### Phase 4: Chain Integration
- Added DeclinedReceiptStep to Sale.java chain
- Positioned before AddRecordStep (prevents database save)
- Returns false to stop chain (prevents duplicate receipts)

### Phase 5: Rollout to All Transactions
- Added DeclinedReceiptStep to 7 other transaction types
- Ensured consistent behavior across all transactions
- Maintained backward compatibility for approved transactions

---

## Key Design Principles

### 1. **Single Responsibility**
- ReadCardStep: Detect declined transactions
- DeclinedReceiptStep: Handle receipt printing
- PrintViewModel: Generate receipt bitmap

### 2. **Non-Invasive**
- Approved transactions unaffected
- Failed transactions unaffected
- Only declined transactions have new behavior

### 3. **No Database Impact**
- Declined receipts never saved
- Temporary Record objects only
- Database integrity maintained

### 4. **Clear Logging**
- DeclinedReceiptStep logs all decisions
- Useful for debugging
- Tracks receipt printing success/failure

---

## Testing Scenarios

### Scenario 1: Approved Transaction (Code "00")
- ✓ Full detailed receipt printed
- ✓ Saved to database
- ✓ DeclinedReceiptStep skipped

### Scenario 2: Declined Transaction (Code "01")
- ✓ Minimal VOID receipt printed
- ✓ NOT saved to database
- ✓ DeclinedReceiptStep executes and prints

### Scenario 3: Network Failure
- ✓ No receipt printed
- ✓ NOT saved to database
- ✓ DeclinedReceiptStep skipped

### Scenario 4: Card Read Error
- ✓ No receipt printed
- ✓ NOT saved to database
- ✓ DeclinedReceiptStep skipped

---

## Summary

This implementation provides a **complete solution for printing minimal receipts for declined transactions** without saving to the database. The key innovation is the **ReadCardStep check** that distinguishes between:

1. **Server decline + EMV error** → Continue chain for receipt
2. **Actual card processing failure** → Stop chain, no receipt

This single decision point at ReadCardStep makes the entire system work elegantly, allowing DeclinedReceiptStep to execute only when needed, ensuring users get a receipt for their declined transactions while maintaining data integrity.
