# Dual Event Logging Implementation Guide

## Overview

This document describes the implementation of dual event logging for QR validation events in the UPI PSP Platform. The implementation ensures that QR validation events are logged to both `qr_validation_events` and `transaction_events` tables when a transaction is linked to a QR validation.

## Business Requirement

**Problem**: QR validation events were only logged to `qr_validation_events`, making it difficult to track the complete transaction timeline in a single place.

**Solution**: Implement dual logging that:
- ✅ Maintains existing `qr_validation_events` for audit purposes
- ✅ Adds QR validation events to `transaction_events` for unified timeline
- ✅ Preserves hash chain integrity in both event streams
- ✅ Logs events immediately when they occur
- ✅ Uses QR validation ID references in transaction events

## Implementation Details

### 1. Core Function: `append_event_with_transaction_link/3`

**Location**: `lib/da_product_app/qr_validation/services/qr_validation_service.ex`

```elixir
@spec append_event_with_transaction_link(binary(), atom(), map()) :: {:ok, QRValidationEvent.t()} | {:error, any()}
def append_event_with_transaction_link(qr_validation_id, event_type, payload)
```

**Purpose**: Enhanced event logging that logs to both event tables when a transaction is linked.

**Flow**:
1. Logs event to `qr_validation_events` (existing behavior)
2. Attempts to find linked transaction via `parent_qr_validation_id`
3. If transaction found, logs corresponding event to `transaction_events`
4. Returns result from QR validation event logging

### 2. Event Type Mapping

**Function**: `map_qr_event_to_transaction_event/1`

Maps QR validation event types to transaction event types:

| QR Validation Event | Transaction Event |
|-------------------|------------------|
| `:valqr_received` | `:qr_validation_received` |
| `:valqr_validated` | `:qr_validation_completed` |
| `:valqr_updated` | `:qr_validation_updated` |
| `:valqr_failed` | `:qr_validation_failed` |

### 3. Transaction Lookup

**Function**: `find_transaction_by_qr_validation/1`

```elixir
from(t in Transaction, 
  where: t.parent_qr_validation_id == ^qr_validation_id)
|> Repo.one()
```

Uses existing `parent_qr_validation_id` foreign key relationship.

### 4. Transaction Event Creation

**Function**: `append_transaction_event/3`

Creates transaction events with:
- Proper sequence numbering
- Hash chain integrity 
- QR validation reference in payload

**Transaction Event Payload Structure**:
```elixir
%{
  qr_validation_id: qr_validation_id,
  qr_validation_status: Map.get(qr_payload, :status),
  qr_event_type: Atom.to_string(qr_event_type)
}
```

## Integration Points

### 1. QR Validation Creation

**File**: `create_validation/1` in `QRValidationService`

**Before**:
```elixir
|> Multi.run(:event, fn _repo, %{qr_validation: v} ->
  append_event(v.id, :valqr_received, %{status: v.status})
end)
```

**After**:
```elixir
|> Multi.run(:event, fn _repo, %{qr_validation: v} ->
  append_event_with_transaction_link(v.id, :valqr_received, %{status: v.status})
end)
```

### 2. QR Validation Updates

**File**: `update_qr_with_resp/2` in `QRValidationService`

**Before**:
```elixir
_ = append_event(updated.id, :valqr_updated, %{status: updated.status, quotes: length(fx_quotes)})
```

**After**:
```elixir
_ = append_event_with_transaction_link(updated.id, :valqr_updated, %{status: updated.status, quotes: length(fx_quotes)})
```

## Database Schema

### Existing Tables

**qr_validation_events**:
```sql
CREATE TABLE qr_validation_events (
  id BIGINT PRIMARY KEY,
  qr_validation_id BIGINT REFERENCES qr_validations(id),
  seq INTEGER NOT NULL,
  event_type VARCHAR(255) NOT NULL,
  payload JSON,
  prev_hash BINARY,
  hash BINARY NOT NULL,
  inserted_at DATETIME NOT NULL
);
```

**transaction_events**:
```sql
CREATE TABLE transaction_events (
  id BIGINT PRIMARY KEY,
  transaction_id BIGINT REFERENCES transactions(id),
  seq INTEGER NOT NULL,
  event_type VARCHAR(255) NOT NULL,
  payload JSON,
  prev_hash BINARY,
  hash BINARY NOT NULL,
  inserted_at DATETIME NOT NULL
);
```

**Link Table**: `transactions.parent_qr_validation_id` → `qr_validations.id`

## Event Timeline Example

### Scenario: QR Validation with Linked Transaction

1. **Transaction Created**:
   ```
   transaction_events: seq=1, event_type="transaction_created"
   ```

2. **QR Validation Received**:
   ```
   qr_validation_events: seq=1, event_type="valqr_received"
   transaction_events: seq=2, event_type="qr_validation_received"
   ```

3. **QR Validation Updated**:
   ```
   qr_validation_events: seq=2, event_type="valqr_updated"  
   transaction_events: seq=3, event_type="qr_validation_updated"
   ```

4. **Payment Processing Continues**:
   ```
   transaction_events: seq=4, event_type="debit_secured"
   transaction_events: seq=5, event_type="credit_success"
   ```

## Error Handling

### Graceful Degradation

- If transaction lookup fails: Only logs to `qr_validation_events`
- If transaction event creation fails: QR validation event still succeeds
- Logs debug messages for troubleshooting

### Transaction Safety

- Uses `Ecto.Multi` for atomic operations
- Rollback on any failure in the dual logging transaction
- Preserves data integrity across both event tables

## Logging and Monitoring

### Debug Logs

```elixir
Logger.debug("No transaction found for QR validation ID: #{qr_validation_id}")
```

### Event Chain Integrity

Both event streams maintain cryptographic hash chains:
```elixir
hash = :crypto.hash(:sha256, :erlang.term_to_binary({prev_hash, seq, event_type, payload, ts}))
```

## Testing Strategy

### Manual Testing

1. **Create QR Validation**: Verify event in `qr_validation_events` only
2. **Create Linked Transaction**: Set `parent_qr_validation_id`
3. **Trigger QR Events**: Verify events in both tables
4. **Verify Sequencing**: Check proper `seq` values in both chains
5. **Verify Payload**: Ensure QR validation references in transaction events

### Unit Tests Location

- `test/da_product_app/qr_validation/services/qr_validation_service_test.exs`
- `test/da_product_app/transactions/transaction_service_test.exs`

### Test Cases

1. **Dual Logging Success**: QR validation with linked transaction
2. **Single Logging**: QR validation without linked transaction  
3. **Event Type Mapping**: Verify correct event type conversion
4. **Hash Chain Integrity**: Verify hash chains in both tables
5. **Error Scenarios**: Transaction lookup failures, etc.

## Performance Considerations

### Database Impact

- **Additional Queries**: One extra SELECT per QR validation event
- **Additional Inserts**: One extra INSERT when transaction is linked
- **Index Usage**: Leverages existing `parent_qr_validation_id` index

### Optimization

- Transaction lookup uses single query with index
- No N+1 query problems
- Atomic operations minimize database round trips

## Migration Notes

### Backward Compatibility

- ✅ No breaking changes to existing APIs
- ✅ Existing `qr_validation_events` behavior preserved
- ✅ Old QR validations without transactions continue to work
- ✅ No schema migrations required

### Rollout Strategy

1. Deploy code changes
2. Monitor logs for dual logging activity
3. Verify events in both tables for new QR validations
4. Monitor performance metrics

## Configuration

### Feature Flags

No feature flags required - implementation is backward compatible.

### Environment Variables

Uses existing database and logging configuration.

## Troubleshooting

### Common Issues

1. **No Transaction Events**: Check `parent_qr_validation_id` is set correctly
2. **Hash Chain Errors**: Verify event sequence integrity
3. **Performance Issues**: Monitor transaction lookup query performance

### Debug Commands

```sql
-- Check QR validation events
SELECT * FROM qr_validation_events WHERE qr_validation_id = ?;

-- Check related transaction events  
SELECT te.* FROM transaction_events te
JOIN transactions t ON te.transaction_id = t.id  
WHERE t.parent_qr_validation_id = ?;

-- Verify linkage
SELECT qv.id as qr_id, t.id as txn_id, t.parent_qr_validation_id
FROM qr_validations qv
LEFT JOIN transactions t ON t.parent_qr_validation_id = qv.id
WHERE qv.id = ?;
```

## Benefits Achieved

### Business Benefits

✅ **Unified Transaction Timeline**: All events in `transaction_events`  
✅ **Complete Audit Trail**: QR-specific events preserved in `qr_validation_events`  
✅ **Real-time Monitoring**: Events logged immediately when they occur  
✅ **Data Integrity**: Hash chains maintained in both event streams  

### Technical Benefits

✅ **Backward Compatibility**: No breaking changes  
✅ **Graceful Degradation**: Works with or without linked transactions  
✅ **Performance Efficient**: Minimal additional database load  
✅ **Maintainable**: Clean separation of concerns  

## Future Enhancements

### Potential Improvements

1. **Batch Processing**: Group multiple events for bulk processing
2. **Event Replay**: Capability to rebuild transaction events from QR events
3. **Analytics**: Enhanced reporting across unified event timeline
4. **Real-time Streaming**: Push events to message queues for real-time processing

---

## ReqPay Dual-Logging

### Overview

In addition to QR validation dual-logging described above, ReqPay lifecycle events are now dual-logged: they continue to be stored in `req_pay_events` (the per-aggregate chain used by the UI and reconciliation flows) and are also appended to the canonical `transaction_events` timeline when the ReqPay is linked to a transaction.

### Implementation Location

The ReqPay dual-logging implementation lives in:

- `lib/da_product_app/transactions/req_pay_service.ex`

Specifically, the internal helper `add_to_transaction_events/2` (called from the ReqPay event append flow) now does two things:

1. Calls the existing `TransactionEventChainService.append_event/3` (preserves the existing per-aggregate chain behavior).
2. Attempts a canonical append via the central transaction helper (reused from `transaction_service.ex`) to insert a corresponding `transaction_events` record. The mapped transaction event type uses the convention `:req_pay_<event_name>` (for example, a `:req_pay_created` or `:req_pay_confirmed`).

### Behavior and Error Handling

- The canonical append is best-effort: if the transaction lookup or canonical insert fails for any reason, the ReqPay event still succeeds and the failure is logged (non-fatal). This preserves availability of the ReqPay flow while providing a unified transaction timeline when possible.

- The canonical transaction payload includes a reference to the `req_pay_id` and the `req_pay_event_seq` so the event can be traced back to the originating ReqPay chain.

### Event Type Mapping

ReqPay event types are mapped to transaction event types using the simple prefixing rule:

- req_pay event `:created` -> transaction event `:req_pay_created`
- req_pay event `:confirmed` -> transaction event `:req_pay_confirmed`
- req_pay event `:failed` -> transaction event `:req_pay_failed`

This keeps naming consistent and easy to filter in the canonical timeline.

### Tests

A focused unit test was added to verify presence of the canonical transaction event when a ReqPay event is appended:

- `test/da_product_app/transactions/req_pay_dual_logging_test.exs`

The test creates a minimal transaction and a ReqPay linked to it (using direct Repo insert to avoid unrelated query paths), appends a ReqPay event, and asserts that both the `req_pay_event` and a `transaction_event` with the mapped `req_pay_*` type exist.

---

## ReqChkTxn Dual-Logging

### Overview

ReqChkTxn lifecycle events (the `req_chk_txn_events` chain) are now dual-logged: they remain stored in `req_chk_txn_events` and are also appended to the canonical `transaction_events` timeline when the `req_chk_txn` is linked to a `transaction` (i.e., `transaction_id` is present).

### Implementation Location

- `lib/da_product_app/transactions/req_chk_txn_service.ex`

The service helper `add_to_transaction_events/2` appends the per-aggregate chain entry (via `TransactionEventChainService.append_event/3`) and then attempts a canonical append via the central transaction helper (`DaProductApp.Transactions.Service.append_event/3`).

### Behavior and Error Handling

- The canonical append is best-effort and non-fatal: failures are logged and do not prevent ReqChkTxn processing.
- The canonical transaction event payload includes `req_chk_txn_id` and `req_chk_txn_event_seq` to allow tracing back to the per-aggregate event.

### Event Type Mapping

ReqChkTxn event types are mapped with the prefix rule:

- req_chk_txn event `:received` -> transaction event `:req_chk_txn_received`
- req_chk_txn event `:checked` -> transaction event `:req_chk_txn_checked`
- req_chk_txn event `:failed` -> transaction event `:req_chk_txn_failed`

### Tests

A focused unit test was added:

- `test/da_product_app/transactions/req_chk_txn_dual_logging_test.exs`

The test creates a minimal transaction, inserts a `req_chk_txn` linked to it, appends a req_chk_txn event, and asserts that both the per-aggregate `req_chk_txn_event` and a `transaction_event` with the mapped `req_chk_txn_*` type exist.


**Implementation Date**: September 23, 2025  
**Author**: GitHub Copilot  
**Version**: 1.0  
**Status**: ✅ COMPLETE