# PRD: UPI Core (Shared Business Logic & Normalization)

**Version**: 1.0  
**Date**: 2026-06-12  
**Status**: Active  

---

## 1. Executive Summary

**UPI Core** is the centralized business logic layer providing normalization, validation, and processing for all UPI payment operations. It abstracts NPCI API complexity and provides a unified interface for ReqValQr/ReqPay/ReqChkTxn processing across dynamic and static QR flows, with full support for international payments, multi-currency handling, and immutable audit trails.

---

## 2. Product Overview

### 2.1 Purpose
- **Request Normalization**: Standardize NPCI XML inputs into canonical data format
- **Validation Layer**: Comprehensive input validation (XML, payload, business rules)
- **QR Processing**: Parse and validate QR payloads for dynamic/static types
- **Payment Processing**: Normalize ReqPay requests with status tracking
- **Status Queries**: Handle ReqChkTxn (transaction status check) requests
- **Verification Tokens**: Generate and validate UUIDs for QR session management
- **FX Conversion**: Handle multi-currency to INR conversion
- **Event Chain**: Create immutable audit trails with hash linking
- **Merchant Lookup**: Resolve payee addresses to merchant records
- **Corridor Routing**: Route international payments to correct processor

### 2.2 Key Users
- **upi_dynamic Module**: Uses core for dynamic QR normalization
- **upi_static Module**: Uses core for static QR normalization
- **upi_settlement Module**: Uses core for settlement calculations
- **upi_web Module**: Uses core for dashboard/reporting queries
- **Integration Partners**: API clients normalizing data

---

## 3. Core Features

### 3.1 ReqValQr Processing (QR Validation)
- **XML Parsing**: Parse NPCI ReqValQr XML format
- **Payload Extraction**: Extract merchant, amount, expiry, payer data
- **QR Type Detection**: Identify dynamic (initiation_mode=16) vs static (02/15)
- **Verification Token Generation**: Create unique UUID per validation request
- **Token Storage**: Persist token with 30-minute expiry (dynamic) or 365-day (static)
- **Domestic Validation**: Validate domestic INR-only payments
- **International Validation**: Detect corridor from payee address prefix
- **Base Amount Tracking**: Store original currency amount before FX conversion
- **Expiry Calculation**: Set qr_expires_at based on QR medium
- **Event Creation**: Generate initial QRValidationEvent with hash

### 3.2 ReqPay Processing (Payment Request)
- **XML Parsing**: Parse NPCI ReqPay XML format
- **Token Validation**: Lookup verification token from ReqValQr
- **Idempotency Check**: Detect duplicate requests via msg_id
- **Status Routing**: Route to correct status (SUCCESS/FAILED/PENDING)
- **Amount Validation**: Check amount matches original quote
- **Currency Matching**: Verify currency consistency across corridors
- **Payer/Payee Validation**: Validate UPI addresses
- **XML Hash Computation**: Calculate SHA256 for audit trail
- **Event Chain**: Append event to transaction event chain
- **Response Generation**: Create RespPay with status and timestamp

### 3.3 ReqChkTxn Processing (Status Checks)
- **Transaction Lookup**: Find transaction by transaction ID
- **Status Retrieval**: Return current payment status
- **Event History**: Provide transaction event chain
- **Settlement Tracking**: Show settlement batch assignment
- **Dispute Status**: Indicate if in chargeback/representment
- **Response Format**: Standardized status response

### 3.4 Domestic Payment Flow
- **INR-only Processing**: Direct INR amount without FX conversion
- **Merchant Validation**: Lookup merchant by payee VPA
- **Amount Limits**: Enforce dynamic (8000 INR) or static (200 INR) limits
- **Simple Routing**: Direct NPCI delivery without corridors
- **Settlement**: Standard INR settlement process

### 3.5 International Payment Flow
- **Corridor Detection**: Identify international payment from payee prefix
- **Supported Corridors**: Singapore ($), UAE (AED), USA ($), extensible
- **Base Currency Handling**: Accept SGD/AED/USD amounts
- **FX Rate Lookup**: Get current rate + markup percentage
- **INR Conversion**: Calculate inr_amount = base_amount × rate × (1 + markup)
- **Corridor-Specific Routing**: Route to correct international processor
- **Settlement in INR**: Convert back to INR in settlement batch
- **Cross-Border Compliance**: Handle regulatory requirements per corridor

### 3.6 FX Rate Management
- **Rate Fetching**: Get current rates from external FX service
- **Rate Caching**: Cache rates for 1 hour to avoid multiple lookups
- **Markup Application**: Apply corridor-specific markup percentage
- **FX Quote Creation**: Immutable quote record per transaction
- **Historical Tracking**: Track rate changes over time
- **Effective Date**: Rates effective as of timestamp

### 3.7 Audit & Event Chain
- **Event Types**: Created, validated, processed, settled, disputed, closed
- **Hash Linking**: SHA256(prev_hash + current_event) for chain integrity
- **Immutable Records**: Events never modified, only appended
- **Full Traceability**: Complete transaction history from validation to settlement
- **Regulatory Compliance**: Meet audit requirements for payment processors
- **Event Payload**: Store relevant data (amounts, statuses, errors) in payload

### 3.8 Verification Token Management
- **UUID Generation**: Create unique token per ReqValQr request
- **Token Lifecycle**: 30 min (dynamic) / 365 day (static) validity
- **One-Time Use**: Verify token only used once in ReqPay
- **Idempotency**: Prevent duplicate payments via token + msg_id
- **Token Lookup**: Quick lookup in database for validation

### 3.9 Merchant Lookup & Resolution
- **Address-to-Merchant**: Resolve payee VPA to merchant record
- **Organization Mapping**: Get org_id from merchant profile
- **Merchant Details**: Retrieve category, invoice, timezone
- **Timeout Handling**: Special merchants (gourmet@mercury) trigger timeouts
- **Merchant Validation**: Check merchant status (active/inactive)

### 3.10 Error Detection & Classification
- **Validation Errors**: Invalid XML, missing fields, format errors
- **Business Errors**: Amount exceeds limit (X7), merchant not found (ZR)
- **Session Errors**: Token not found (ZE), duplicate transaction (ZD)
- **System Errors**: Database errors (96), timeout (PE)
- **Acquirer Errors**: IP05/08/10 for partner-specific scenarios

---

## 4. Database Schema

### 4.1 Key Tables
| Table | Purpose | Primary Key |
|-------|---------|-------------|
| qr_validations | QR validation records | msg_id |
| req_pays | Payment requests | msg_id |
| transactions | Core transactions | txn_id |
| qr_validation_events | Event chain for QR | seq, qr_validation_id |
| transaction_events | Event chain for payment | seq, transaction_id |
| fx_quotes | FX rate snapshots | id |
| fx_rates | FX rate master | id |

### 4.2 QRValidation Schema
```
msg_id: unique identifier
txn_id: linked transaction
org_id: merchant organization
payer_addr: customer UPI address
payee_addr: merchant UPI address
qr_medium: DYNAMIC or STATIC
initiation_mode: 16 (dynamic) or 02/15 (static)
ver_token: UUID verification token
validation_type: DOMESTIC or INTERNATIONAL
corridor: SINGAPORE/UAE/USA/null
base_amount: original amount in base currency
base_currency: SGD/AED/USD/INR
inr_amount_calc: calculated INR amount after FX
qr_payload: full QR XML content
qr_expires_at: expiry timestamp
created_at: request timestamp
```

### 4.3 ReqPay Schema
```
msg_id: unique identifier (idempotency)
txn_id: linked transaction
org_id: merchant organization
payer_addr: payer UPI address
payee_addr: payee UPI address
amount: payment amount
currency: payment currency
validation_type: DOMESTIC or INTERNATIONAL
corridor: routing corridor
status: SUCCESS/FAILED/PENDING/REVERSED
base_amount: original base currency amount
fx_rate: applied FX rate
inr_amount_calc: settlement amount in INR
processing_duration_ms: time to process
npci_request_received_at: when NPCI sent request
npci_response_sent_at: when response sent
created_at: timestamp
```

---

## 5. Core Modules & Functions

### 5.1 QRValidation Context
- **create_qr_validation()**: Create QR record from ReqValQr XML
- **domestic_changeset()**: Validation for INR-only payments
- **international_changeset()**: Validation for multi-currency
- **validate_base_amount_for_qr_type()**: Check amount limits
- **validate_fx_calculation()**: Verify FX conversion accuracy
- **find_by_msg_id()**: Lookup by message ID
- **find_by_ver_token()**: Lookup by verification token

### 5.2 ReqPay Service
- **create_req_pay()**: Create payment request from XML
- **update_status()**: Update payment status
- **mark_successful()**: Mark payment as successful
- **mark_failed()**: Mark payment as failed
- **find_by_msg_id()**: Lookup with idempotency check
- **update_req_pay_with_response()**: Store response data

### 5.3 ReqChkTxn Service
- **check_transaction_status()**: Query transaction status
- **get_event_chain()**: Retrieve full event history
- **get_settlement_info()**: Retrieve settlement details

### 5.4 Event Chain Service
- **create_initial_event()**: Create first event in chain
- **append_event()**: Add event to chain
- **compute_xml_hashes()**: Calculate SHA256 hashes
- **verify_chain_integrity()**: Validate hash linking

### 5.5 FX Service
- **get_current_rate()**: Fetch FX rate with cache
- **apply_markup()**: Calculate markup on rate
- **convert_to_inr()**: Base amount × rate × (1 + markup)
- **create_fx_quote()**: Store immutable rate snapshot

### 5.6 Merchant Service
- **resolve_merchant()**: Lookup by payee address
- **get_merchant_details()**: Retrieve merchant info
- **validate_merchant_status()**: Check active status

### 5.7 Corridor Service
- **detect_corridor()**: Identify from payee prefix
- **get_corridor_config()**: Retrieve routing settings
- **validate_for_corridor()**: Check amount limits

---

## 6. Processing Flow

### 6.1 ReqValQr Complete Flow (20 Steps)
1. Receive HTTP POST with XML payload
2. Parse XML structure
3. Extract key fields (msg_id, amount, payee, payer, etc)
4. Detect QR medium (DYNAMIC or STATIC)
5. Validate XML schema
6. Generate verification token (UUID)
7. Determine validation_type (DOMESTIC or INTERNATIONAL)
8. Detect corridor from payee address (if international)
9. Validate base_amount limits (dynamic: 8000, static: 200)
10. Lookup merchant by payee VPA
11. Validate merchant active status
12. For international: Fetch FX rate
13. For international: Calculate INR amount
14. Calculate qr_expires_at (30 min or 365 days)
15. Create event_chain initial event
16. Compute XML hashes for audit
17. Store QRValidation record
18. Store initial event
19. Generate RespValQr XML
20. Send response to NPCI

### 6.2 ReqPay Complete Flow (19 Steps)
1. Receive HTTP POST with XML payload
2. Parse XML structure
3. Extract key fields (msg_id, txn_id, amount, etc)
4. Validate msg_id format
5. Check idempotency (msg_id unique)
6. Lookup verification token
7. Validate token found and not expired
8. Validate token used only once
9. Verify payer/payee addresses
10. Verify amount matches quote
11. Verify currency matches
12. Determine validation_type
13. Get merchant & organization
14. Calculate settlement amount
15. Update transaction status to SUCCESS
16. Append event to transaction_events
17. Compute XML hashes
18. Generate RespPay XML
19. Send response to NPCI

---

## 7. Configuration

### 7.1 Environment Variables
```elixir
# Database
DATABASE_URL=ecto://user:pass@localhost/upi_core

# Amount Limits
DYNAMIC_QR_LIMIT=8000
STATIC_QR_LIMIT=200

# FX Settings
FX_CACHE_TTL_MINUTES=60
FX_MARKUP_SINGAPORE=2.5
FX_MARKUP_UAE=2.0
FX_MARKUP_USA=3.0

# Expiry Windows
DYNAMIC_QR_EXPIRY_MINUTES=30
STATIC_QR_EXPIRY_DAYS=365

# Hash Algorithm
HASH_ALGORITHM=sha256

# Settlement
SETTLEMENT_CUTOFF_TIME=17:30
SETTLEMENT_TIMEZONE=UTC
```

### 7.2 Limits & Constraints
- **Amount Limits**: Dynamic 8000 INR, Static 200 INR
- **Token Expiry**: Dynamic 30 min, Static 365 days
- **Verification**: Token one-time use only
- **Message ID**: Unique per request
- **FX Rate Cache**: 1 hour validity

---

## 8. Error Handling

| Error Code | Scenario | Status |
|-----------|----------|--------|
| 00 | Success | 200 OK |
| PE | Token/QR Expired | 400 |
| X7 | Amount Exceeds Limit | 400 |
| YH | Merchant Validation Failed | 403 |
| ZE | Token Not Found/Invalid | 400 |
| ZD | Duplicate Transaction | 400 |
| ZF | Verification Failed | 403 |
| ZQ | QR Not Found | 404 |
| ZR | Merchant Not Found | 404 |
| ZH | Invalid XML | 400 |
| 05 | Invalid Payload | 400 |
| 96 | System Error | 500 |

---

## 9. API Contracts

### 9.1 ReqValQr Input
```xml
<ReqValQr>
  <MsgID>unique-id</MsgID>
  <Ts>timestamp</Ts>
  <OrgID>MER101</OrgID>
  <Curr>INR</Curr>
  <Amount>1000.00</Amount>
  <Mcc>5411</Mcc>
  <RefUrl>qr-payload-base64</RefUrl>
  ...
</ReqValQr>
```

### 9.2 RespValQr Output
```xml
<RespValQr>
  <MsgID>unique-id</MsgID>
  <Ts>timestamp</Ts>
  <Status>00</Status>
  <VerToken>uuid-token</VerToken>
  <ExpiresAt>timestamp</ExpiresAt>
  ...
</RespValQr>
```

---

## 10. Dependencies

- **phoenix_ecto**: Database layer
- **decimal**: Precise amount calculations
- **timex**: Date/time utilities
- **uuid**: Token generation
- **sweet_xml**: XML parsing
- **poison/jason**: JSON encoding

---

## 11. Testing Requirements

- ✅ Unit tests for all validation functions
- ✅ Integration tests for end-to-end flows
- ✅ FX conversion accuracy tests
- ✅ Event chain integrity tests
- ✅ Error scenario tests
- ✅ Edge case tests (expired tokens, duplicate payments)
- ✅ Performance tests for high throughput

---

## 12. Success Metrics

| Metric | Target | Status |
|--------|--------|--------|
| Validation Success Rate | 99.9% | ✅ |
| Event Chain Integrity | 100% | ✅ |
| FX Accuracy | 99.99% | ✅ |
| Idempotency Prevention | 100% | ✅ |

