# ReqValQr Technical Flow Analysis - UPI Dynamic Module

## Overview

**ReqValQr** is the first API call in the UPI transaction flow. It's initiated by NPCI to validate a merchant's QR code before processing any payment. The PSP (Mercury) receives this request, validates the merchant details, and returns merchant information along with a verification token.

---

## Flow Architecture

### High-Level Sequence

```
NPCI → POST /ReqValQr → Mercury PSP → Immediate ACK (200 OK)
                                    ↓ (Async Task)
                                 Validate QR → Process Details → Send RespValQr → NPCI
```

### HTTP Response Pattern

- **Immediate Response to NPCI**: ACK XML (200 OK) - acknowledges receipt
- **Async Processing**: QR validation happens in background Task
- **Deferred Response**: RespValQr sent to NPCI callback endpoint

---

## Code Flow: Router → Function → Execution

### Complete Request Flow Diagram

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          INCOMING REQUEST                               │
│                    POST /ReqValQr (XML Body)                            │
└────────────────────────────┬────────────────────────────────────────────┘
                             │
                             ▼
        ┌────────────────────────────────────────────┐
        │  Router: apps/upi_dynamic/lib/upi_dynamic  │
        │          /router.ex:13                     │
        │                                            │
        │  post "/ReqValQr/*path",                  │
        │       UpiController, :validate_qr        │
        └────────────────────┬───────────────────────┘
                             │
                             ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │ Function: UpiController.validate_qr/2                          │
  │ File: apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/    │
  │       upi_controller.ex:29                                     │
  │                                                                 │
  │ Steps:                                                          │
  │ 1. read_request_body(conn) → XML String                        │
  │ 2. UpiXmlSchema.parse_req_val_qr(xml_body) → parsed_data      │
  └──────────┬──────────────────────────────────────────────────────┘
             │
             ├─ Is parsing error?
             │  └─ YES → send_error_response(conn, "96", ...)
             │
             └─ NO → Continue
                      │
                      ▼
          ┌──────────────────────────────┐
          │ Check QR Type Detection      │
          │                              │
          │ StaticQrTransactionManager   │
          │ .is_static_qr?(parsed_data) │
          └──────────┬───────────────────┘
                     │
         ┌───────────┴───────────┐
         │                       │
         ▼ YES (Static)          ▼ NO (Dynamic)
    ┌──────────────┐         ┌────────────────────────┐
    │Static QR     │         │ PE Expiry Check        │
    │ Flow         │         │ (Optional Early Check) │
    │              │         │                        │
    │ Route to:    │         │ extract_qr_ts()        │
    │ process_     │         │ DateTime.compare()     │
    │ static_qr_   │         │                        │
    │ flow()       │         │ Expired?               │
    └──────────────┘         │  └─ YES → Send PE      │
                             │       error async      │
                             │       + ACK            │
                             │       + return         │
                             │                        │
                             │  └─ NO → Continue      │
                             └────────┬──────────────┘
                                      │
                                      ▼
                     ┌─────────────────────────────┐
                     │ process_normal_qr_flow()   │
                     │ (Main Dynamic Path)         │
                     └──────────┬──────────────────┘
                                │
                    Step 1: Generate ACK XML
                                │
                                ▼
                     ┌──────────────────────┐
                     │  Send ACK (200 OK)   │
                     │  to NPCI immediately │
                     │                      │
                     │ XML Format:          │
                     │ <Ack reqMsgId="..." │
                     │      ts="..."/>      │
                     └──────────┬───────────┘
                                │
                        Step 2: Return Control
                                │
                                ▼
                     ┌──────────────────────────────────┐
                     │ Task.start(fn -> ... end)        │
                     │ (Background Async Processing)    │
                     └──────────┬───────────────────────┘
                                │
                                ▼
            ┌───────────────────────────────────────────┐
            │ Step 3a: Main Processing                │
            │                                          │
            │ UpiTransactionManager.                  │
            │ process_qr_validation(xml_body)         │
            │                                          │
            │ File: apps/upi_dynamic/lib/upi_dynamic/ │
            │       upi_transaction_manager.ex:45     │
            └────────────┬────────────────────────────┘
                         │
         ┌───────────────┴───────────────┐
         │                               │
         ▼ Path 1: Static QR             ▼ Path 2: Dynamic QR
    ┌──────────────────┐           ┌──────────────────────────┐
    │ try_static_qr_   │           │ process_regular_qr_      │
    │ validation()     │           │ validation()             │
    │                  │           │                          │
    │ 1. Extract tr/tn │           │ 1. validate_merchant_qr()│
    │    from payload  │           │    └─ Extract merchant  │
    │ 2. StaticQr      │           │       details from QR    │
    │    Service.get() │           │                          │
    │ 3. Found?        │           │ 2. Merchants.            │
    │  ├─ YES →        │           │    get_merchant_by_vpa() │
    │  │  process_     │           │    └─ Query DB          │
    │  │  static_qr_   │           │                          │
    │  │  validation() │           │ 3. Error checking:       │
    │  │               │           │    ├─ ZR: Not found     │
    │  └─ NO →         │           │    ├─ ZQ: Malformed     │
    │     {:error}     │           │    └─ OK: Continue      │
    └──────────────────┘           │                          │
                                   │ 4. QRValidationService. │
                                   │    create_validation()  │
                                   │    └─ Store in DB       │
                                   │                          │
                                   │ 5. Extract & validate:  │
                                   │    ├─ Amounts           │
                                   │    ├─ Currencies        │
                                   │    ├─ FX rates          │
                                   │    └─ Merchant info     │
                                   └────────┬────────────────┘
                                            │
                                            ▼
                        ┌──────────────────────────────────┐
                        │ Step 3b: Generate RespValQr     │
                        │                                  │
                        │ Prepare response_data map:      │
                        │ ├─ Message IDs                  │
                        │ ├─ Payee/Merchant details       │
                        │ ├─ Amount (Global/Domestic)    │
                        │ ├─ FX info                       │
                        │ ├─ Verification token           │
                        │ └─ Account details              │
                        │                                  │
                        │ UpiXmlSchema.                    │
                        │ generate_resp_val_qr(response) │
                        │ → respvalqr_xml                 │
                        └────────────┬─────────────────────┘
                                     │
                                     ▼
                        ┌──────────────────────────────────┐
                        │ Step 3c: Store Response Hash     │
                        │                                  │
                        │ QRValidationService.             │
                        │ store_response_xml_hash()        │
                        │                                  │
                        │ Stores in DB:                    │
                        │ └─ response_xml_hash             │
                        │    npci_response_sent_at         │
                        └────────────┬─────────────────────┘
                                     │
                                     ▼
                        ┌──────────────────────────────────┐
                        │ Step 3d: Send RespValQr to NPCI │
                        │                                  │
                        │ Req.post(                        │
                        │   npci_callback_url,             │
                        │   body: respvalqr_xml,           │
                        │   headers: ["XML"]               │
                        │ )                                │
                        │                                  │
                        │ URL: https://precert.nfinite.... │
                        │      /iupi/RespValQr/2.0/       │
                        │      urn:txnid:{txn_id}          │
                        └────────────┬─────────────────────┘
                                     │
                        Response from NPCI?
                                     │
                         ┌───────────┴───────────┐
                         │                       │
                    ▼ Status 200             ▼ Other Status
                 ┌──────────┐           ┌──────────────┐
                 │ SUCCESS  │           │ ERROR LOGGED │
                 │ Parse ACK│           │ Retry?       │
                 │ Log OK   │           │ (Optional)   │
                 └──────────┘           └──────────────┘
```

---

## Request Processing Pipeline

### Entry Point: `UpiController.validate_qr/2`

**Location**: [apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex:29](apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex#L29)

**Router**: [apps/upi_dynamic/lib/upi_dynamic/router.ex:13](apps/upi_dynamic/lib/upi_dynamic/router.ex#L13)
```elixir
post "/ReqValQr/*path", UpiController, :validate_qr
```

---

---

## Detailed Function Call Flow

### Phase 1: Request Parsing & Validation

#### Function 1: `UpiController.validate_qr(conn, _params)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex:29-280](apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex#L29)

**Code Flow**:
```elixir
def validate_qr(conn, _params) do
  # Step 1: Extract XML body from HTTP request
  with {:ok, xml_body} <- read_request_body(conn),
       
       # Step 2: Parse XML to extract fields
       {:ok, parsed_data} <- UpiXmlSchema.parse_req_val_qr(xml_body) do
    
    # Step 3: Check if static or dynamic QR
    if StaticQrTransactionManager.is_static_qr?(parsed_data) do
      process_static_qr_flow(conn, xml_body, parsed_data)
    else
      # Step 4: Extract QRts and check expiry (PE error check)
      qr_ts_string = UpiXmlSchema.extract_qr_ts_from_payload(parsed_data)
      qr_validity_minutes = Application.get_env(:upi_dynamic, :qr_validity_minutes, 30)
      
      # Step 5: Parse QRts timestamp
      case DateTime.from_iso8601(qr_ts_string) do
        {:ok, qr_ts_dt, _offset} ->
          # Step 6: Calculate time difference
          now = DateTime.utc_now()
          diff_seconds = DateTime.diff(now, qr_ts_dt)
          expired_by_window = diff_seconds > qr_validity_minutes * 60
          
          # Step 7: Check if expired
          if expired_by_window do
            # QR is expired → Send PE error response asynchronously
            resp_data = %{err_code: "PE", result: "FAILURE", ...}
            
            case UpiXmlSchema.generate_resp_val_qr(resp_data) do
              {:ok, error_xml} ->
                # Step 8a: Schedule async PE response
                Task.start(fn ->
                  Req.post(npci_callback_url, body: error_xml, ...)
                end)
                
                # Step 8b: Send ACK immediately and return
                ack_xml = "<Ack reqMsgId=\"#{msg_id}\" />"
                conn |> send_resp(200, ack_xml)
            end
          else
            # Step 9: QR not expired → Continue with normal flow
            process_normal_qr_flow(conn, xml_body, parsed_data)
          end
      end
    end
  else
    {:error, :invalid_request} -> send_error_response(conn, "96", ...)
    {:error, parse_error} -> send_error_response(conn, "96", ...)
  end
end
```

---

#### Function 2: `process_normal_qr_flow(conn, xml_body, parsed_data)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex:281-360](apps/upi_dynamic/lib/upi_dynamic/controllers/api/v1/upi_controller.ex#L281)

**Purpose**: Send ACK immediately, then process RespValQr asynchronously

**Code Flow**:
```elixir
defp process_normal_qr_flow(conn, xml_body, parsed_data) do
  msg_id_str = parsed_data.msg_id || "unknown"
  
  # Step 1: Generate ACK XML
  ack_xml = """
  <ns2:Ack xmlns:ns2=\"http://npci.org/upi/schema/\" 
           api=\"ReqValQr\" 
           reqMsgId=\"#{msg_id_str}\" 
           ts=\"#{DateTime.utc_now() |> DateTime.to_iso8601()}\"/>
  """
  
  # Step 2: Send ACK response immediately (200 OK)
  Logger.info("Sending ACK for ReqValQr with msgId=#{msg_id_str}")
  
  conn = conn
         |> put_resp_content_type("application/xml")
         |> send_resp(200, ack_xml)
  
  # Step 3: Start background Task for async processing
  Task.start(fn ->
    # Step 3a: Call main validation processor
    case UpiTransactionManager.process_qr_validation(xml_body) do
      {:ok, {respvalqr_xml, qr_validation}} ->
        Logger.info("[RespValQr] Generated successfully")
        
        # Step 3b: Store response XML hash in database
        case QRValidationService.store_response_xml_hash(qr_validation, respvalqr_xml) do
          {:ok, updated_qr_validation} ->
            # Step 3c: Prepare NPCI callback URL
            npci_qr_endpoint = Application.get_env(
              :da_product_app,
              :npci_qr_validation_endpoint,
              "https://precert.nfinite.in/iupi/RespValQr/2.0/urn:txnid:"
            )
            npci_callback_url = "#{npci_qr_endpoint}#{parsed_data.txn_id}"
            
            headers = [
              {"content-type", "application/xml"},
              {"accept", "application/xml"}
            ]
            
            # Step 3d: Send RespValQr to NPCI callback
            case Req.post(npci_callback_url,
                   body: respvalqr_xml,
                   headers: headers,
                   receive_timeout: 30_000
                 ) do
              {:ok, %Req.Response{status: 200}} ->
                Logger.info("[RespValQr] Successfully sent to NPCI")
                
              {:ok, %Req.Response{status: status}} ->
                Logger.error("[RespValQr] NPCI returned status #{status}")
                
              {:error, reason} ->
                Logger.error("[RespValQr] Failed to send: #{inspect(reason)}")
            end
        end
        
      {:error, reason} ->
        Logger.error("[RespValQr] Validation failed: #{inspect(reason)}")
    end
  end)
  
  # Step 4: Return conn (already sent to client)
  conn
end
```

---

### Phase 2: Core Validation Processing

#### Function 3: `UpiTransactionManager.process_qr_validation(qr_data)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex:45-95](apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex#L45)

**Purpose**: Main entry point for QR validation logic

**Code Flow**:
```elixir
def process_qr_validation(qr_data) do
  # Step 1: Parse ReqValQr XML
  case UpiXmlSchema.parse_req_val_qr(qr_data) do
    {:ok, parsed_data_raw} ->
      # Step 2: Normalize parsed data (ensure atom keys)
      parsed_data = normalize_parsed_data(parsed_data_raw)
      
      # Step 3: Attempt static QR validation first
      case try_static_qr_validation(parsed_data, qr_data) do
        {:ok, {xml, qr_validation}} ->
          # Static QR successful
          {:ok, {xml, qr_validation}}
        
        {:error, :not_static_qr} ->
          # Step 4: Not a static QR, try dynamic QR
          Logger.info("Not static QR, proceeding with regular validation")
          
          case process_regular_qr_validation(parsed_data, qr_data) do
            {:ok, {xml, qr_validation}} -> {:ok, {xml, qr_validation}}
            {:ok, xml} -> {:ok, {xml, nil}}
            xml when is_binary(xml) -> {:ok, {xml, nil}}
            {:error, reason} -> {:error, reason}
          end
        
        {:error, reason} ->
          # Step 5: Static QR validation error
          Logger.error("Static QR validation failed: #{inspect(reason)}")
          case generate_error_response(parsed_data, "02", "Error: #{reason}") do
            {:ok, xml} -> {:error, {xml, "Static QR failed"}}
            {:error, r} -> {:error, r}
          end
      end
    
    {:error, reason} ->
      # Step 6: XML parsing failed
      default_parsed_data = %{msg_id: generate_message_id()}
      case generate_error_response(default_parsed_data, "ZH", "Invalid XML") do
        {:ok, xml} -> {:error, {xml, "Invalid XML"}}
        {:error, r} -> {:error, r}
      end
  end
end
```

---

#### Function 4a: `try_static_qr_validation(parsed_data, qr_data)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex:120-160](apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex#L120)

**Purpose**: Extract tr/tn from QR and lookup static QR in database

**Code Flow**:
```elixir
defp try_static_qr_validation(parsed_data, qr_data) do
  qr_payload = parsed_data.qr_payload || ""
  
  if qr_payload != "" do
    # Step 1: Extract transaction reference (tr) and transaction number (tn)
    case StaticQrService.extract_qr_identifiers(qr_payload) do
      {:ok, %{tr: tr, tn: tn}} ->
        Logger.info("=== TRYING STATIC QR VALIDATION ===")
        Logger.info("Extracted tr: #{tr}, tn: #{tn}")
        
        # Step 2: Lookup static QR by tr/tn
        case StaticQrService.get_by_tr_tn(tr, tn) do
          %UpiCore.QRValidation.StaticQr{} = static_qr ->
            # Step 3: Static QR found
            Logger.info("✅ Static QR found: #{static_qr.qr_id}")
            
            # Step 4: Process static QR validation
            process_static_qr_validation(static_qr, parsed_data, qr_data)
          
          nil ->
            # Step 5: Static QR not found
            Logger.info("❌ No static QR found for tr: #{tr}, tn: #{tn}")
            {:error, :not_static_qr}
        end
      
      {:error, _reason} ->
        # Step 6: Cannot extract tr/tn (not static QR format)
        {:error, :not_static_qr}
    end
  else
    {:error, :not_static_qr}
  end
end
```

---

#### Function 4b: `process_regular_qr_validation(parsed_data, qr_data)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex:161-280](apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex#L161)

**Purpose**: Main dynamic QR validation flow

**Code Flow**:
```elixir
defp process_regular_qr_validation(parsed_data, qr_data) do
  # Step 1: Extract merchant details from QR payload
  case validate_merchant_qr(parsed_data.qr_payload, parsed_data.initiation_mode) do
    {:ok, merchant_info} ->
      Logger.info("Merchant QR validated successfully")
      
      # Step 2: Prepare QR validation record attributes
      qr_validation_attrs = %{
        txn_id: parsed_data.txn_id,
        msg_id: parsed_data.msg_id,
        org_id: parsed_data.org_id,
        payer_addr: parsed_data.payer_addr,
        payer_name: parsed_data.payer_name,
        payee_addr: merchant_info.merchant_vpa,
        payee_name: merchant_info.brand_name,
        base_amount: extract_amount_from_qr(parsed_data.qr_payload) || "0.00",
        base_currency: extract_currency_from_qr(parsed_data.qr_payload) || "INR",
        fx_rate: "1.00",
        markup_pct: merchant_info.markup_rate || "0.00",
        ver_token: generate_ver_token(),
        status: "validated",
        validation_type: determine_validation_type(parsed_data),
        corridor: determine_corridor_from_context(parsed_data, ...),
        merchant_category: merchant_info.merchant_category || "5411",
        initiation_mode: parsed_data.initiation_mode,
        raw_xml: qr_data,
        qr_payload: parsed_data.qr_payload
      }
      
      # Step 3: Store QR validation in database
      case QRValidationService.create_validation(qr_validation_attrs) do
        {:ok, qr_validation} ->
          Logger.info("QR validation stored with ID: #{qr_validation.id}")
          
          # Step 4: Extract amount variants
          merchant_amount = extract_merchant_amount_from_qr(parsed_data.qr_payload) || "0.00"
          merchant_currency = extract_merchant_currency_from_qr(parsed_data.qr_payload) || "INR"
          inr_debit_amount = extract_inr_debit_amount_from_qr(parsed_data.qr_payload) || "0.00"
          
          # Step 5: Determine response amount based on purpose
          purpose = parsed_data.purpose || "11"
          {resp_amount, resp_currency} = if purpose == "11" do
            {merchant_amount, merchant_currency}  # Global UPI: use merchant currency
          else
            {inr_debit_amount, "INR"}  # Domestic UPI: use INR
          end
          
          Logger.info("[RespValQr Amount] purpose=#{purpose}, amount=#{resp_amount}, currency=#{resp_currency}")
          
          # Step 6: Prepare response data for RespValQr
          response_data = %{
            org_id: get_psp_org_id(),
            msg_id: generate_message_id(),
            req_msg_id: parsed_data.msg_id,
            result: "SUCCESS",
            err_code: "00",
            txn_id: parsed_data.txn_id,
            payee_addr: merchant_info.merchant_vpa,
            payee_name: merchant_info.brand_name,
            payee_type: "ENTITY",
            merchant_code: merchant_info.mid,
            country_code: merchant_info.country || "IN",
            net_inst_id: "MER1010001",
            amount: resp_amount,
            currency: resp_currency,
            base_amount: merchant_amount,
            base_currency: merchant_currency,
            fx_active: "Y",
            fx_rate: merchant_info.fx_rate || "1.00",
            markup_percentage: merchant_info.markup_rate || "0.00",
            ver_token: qr_validation.ver_token,
            ifsc_code: merchant_info.ifsc_code,
            account_type: merchant_info.account_type,
            account_number: merchant_info.account_number,
            merchant_id: merchant_info.mid,
            store_id: merchant_info.sid,
            terminal_id: merchant_info.tid,
            ...
          }
          
          # Step 7: Generate RespValQr XML
          case UpiXmlSchema.generate_resp_val_qr(response_data) do
            {:ok, respvalqr_xml} -> {:ok, {respvalqr_xml, qr_validation}}
            {:error, reason} -> {:error, reason}
          end
        
        {:error, reason} ->
          # Database storage failed
          case generate_error_response(parsed_data, "02", "Storage failed: #{reason}") do
            {:ok, xml} -> {:error, {xml, "Storage failed"}}
            {:error, r} -> {:error, r}
          end
      end
    
    {:error, "QR_NOT_FOUND"} ->
      # Step 8: QR payload malformed
      case generate_error_response(parsed_data, "ZQ", "QR not found") do
        {:ok, xml} -> {:error, {xml, "QR not found"}}
        {:error, r} -> {:error, r}
      end
    
    {:error, "MERCHANT_NOT_FOUND"} ->
      # Step 9: Merchant VPA not in database
      case generate_error_response(parsed_data, "ZR", "Merchant not found") do
        {:ok, xml} -> {:error, {xml, "Merchant not found"}}
        {:error, r} -> {:error, r}
      end
    
    {:error, "TID_MISMATCH"} ->
      # Step 10: Terminal ID mismatch
      case generate_error_response(parsed_data, "XW", "Terminal ID mismatch") do
        {:ok, xml} -> {:error, {xml, "TID mismatch"}}
        {:error, r} -> {:error, r}
      end
    
    {:error, reason} ->
      # Generic QR validation error
      case generate_error_response(parsed_data, "05", "Invalid QR: #{reason}") do
        {:ok, xml} -> {:error, {xml, "Invalid QR"}}
        {:error, r} -> {:error, r}
      end
  end
end
```

---

#### Function 5: `validate_merchant_qr(qr_payload, initiation_mode)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex - Helper Functions](apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex)

**Purpose**: Extract merchant details from QR payload and validate against database

**Code Flow**:
```elixir
defp validate_merchant_qr(qr_payload, initiation_mode) do
  # Step 1: Extract merchant details from QR payload
  # Format: upi://pay?pa=merchant@bank&pn=Name&mid=MID&tid=TID&sid=SID
  
  merchant_vpa = extract_merchant_vpa_from_qr(qr_payload)
  # Result: "merchant@bank"
  
  merchant_id = extract_merchant_id_from_qr(qr_payload)
  # Result: "MERCHANT_ID"
  
  terminal_id = extract_terminal_id_from_qr(qr_payload)
  # Result: "TERMINAL_ID"
  
  store_id = extract_store_id_from_qr(qr_payload)
  # Result: "STORE_ID"
  
  # Step 2: Validate inputs
  if merchant_vpa == nil or merchant_vpa == "" do
    {:error, "QR_NOT_FOUND"}  # No merchant VPA in payload
  else
    # Step 3: Lookup merchant in database by VPA
    case Merchants.get_merchant_by_vpa(merchant_vpa) do
      nil ->
        {:error, "MERCHANT_NOT_FOUND"}  # Merchant VPA doesn't exist
      
      merchant ->
        # Step 4: Validate terminal ID if provided
        if terminal_id && merchant.tid && terminal_id != merchant.tid do
          Logger.warning("TID mismatch: payload=#{terminal_id}, db=#{merchant.tid}")
          {:error, "TID_MISMATCH"}  # Terminal ID doesn't match
        else
          # Step 5: Success - return merchant info
          {:ok, %{
            merchant_vpa: merchant.vpa,
            brand_name: merchant.brand_name,
            legal_name: merchant.legal_name,
            mid: merchant.mid,
            tid: merchant.tid,
            sid: merchant.sid,
            ifsc_code: merchant.ifsc_code,
            account_number: merchant.account_number,
            account_type: merchant.account_type,
            merchant_category: merchant.merchant_category,
            merchant_type: merchant.merchant_type,
            fx_rate: merchant.fx_rate,
            markup_rate: merchant.markup_rate,
            country: merchant.country,
            onboarding_type: merchant.onboarding_type,
            pincode: merchant.pincode,
            city: merchant.city,
            tier: merchant.tier
          }}
        end
    end
  end
end
```

---

#### Function 6: `generate_resp_val_qr(response_data)`
**File**: apps/upi_core/lib/upi_core/upi_xml_schema.ex

**Purpose**: Generate XML-compliant RespValQr response

**Code Flow**:
```elixir
def generate_resp_val_qr(response_data) when is_map(response_data) do
  # Step 1: Validate required fields
  required_fields = [:org_id, :msg_id, :txn_id, :result, :err_code]
  
  missing = Enum.filter(required_fields, &(Map.get(response_data, &1) == nil))
  if missing != [] do
    {:error, "Missing required fields: #{inspect(missing)}"}
  else
    # Step 2: Build XML structure
    try do
      xml = """
      <?xml version=\"1.0\" encoding=\"UTF-8\"?>
      <RespValQr xmlns=\"http://npci.org/upi/schema/\">
        <Resp
          id=\"#{Map.get(response_data, :id, generate_resp_id())}\"
          ts=\"#{Map.get(response_data, :ts, DateTime.utc_now() |> DateTime.to_iso8601())}\"
          orgId=\"#{Map.get(response_data, :org_id)}\"
          msgId=\"#{Map.get(response_data, :msg_id)}\"
          reqMsgId=\"#{Map.get(response_data, :req_msg_id)}\"
          txnId=\"#{Map.get(response_data, :txn_id)}\"
          result=\"#{Map.get(response_data, :result)}\"
          errCode=\"#{Map.get(response_data, :err_code)}\"
        >
          <!-- For SUCCESS: Include Ref with Merchant details -->
          #{if Map.get(response_data, :result) == "SUCCESS" do
            """
            <Ref
              id=\"#{Map.get(response_data, :ref_id)}\"
              desc=\"Merchant Validation\"
            >
              <Merchant
                payeeAddr=\"#{Map.get(response_data, :payee_addr)}\"
                payeeName=\"#{Map.get(response_data, :payee_name)}\"
                amount=\"#{Map.get(response_data, :amount)}\"
                currency=\"#{Map.get(response_data, :currency)}\"
                verToken=\"#{Map.get(response_data, :ver_token)}\"
                merchantId=\"#{Map.get(response_data, :merchant_id)}\"
                ifscCode=\"#{Map.get(response_data, :ifsc_code)}\"
                accountNumber=\"#{Map.get(response_data, :account_number)}\"
              />
            </Ref>
            """
          else
            ""
          end}
        </Resp>
      </RespValQr>
      """
      
      {:ok, xml}
    rescue
      e ->
        {:error, "XML generation failed: #{inspect(e)}"}
    end
  end
end
```

---

### Phase 3: Error Handling Flows

#### Error Handler 1: PE Error (Payment Expired)
**File**: [apps/upi_dynamic/lib/upi_dynamic/error_handlers/pe_error_handler.ex](apps/upi_dynamic/lib/upi_dynamic/error_handlers/pe_error_handler.ex)

**Triggered in**: `UpiController.validate_qr` (lines 81-120)

**Code Flow**:
```elixir
# In validate_qr controller:
qr_ts_string = UpiXmlSchema.extract_qr_ts_from_payload(parsed_data)
qr_validity_minutes = Application.get_env(:upi_dynamic, :qr_validity_minutes, 30)

case DateTime.from_iso8601(qr_ts_string) do
  {:ok, qr_ts_dt, _offset} ->
    now = DateTime.utc_now()
    diff_seconds = DateTime.diff(now, qr_ts_dt)
    diff_minutes = diff_seconds / 60
    
    expired_by_window = diff_seconds > qr_validity_minutes * 60
    
    if expired_by_window do
      # QR is expired - generate PE error
      resp_data = %{
        org_id: Map.get(parsed_data, :org_id, "MER101"),
        msg_id: generate_message_id(),
        req_msg_id: parsed_data.msg_id,
        result: "FAILURE",
        err_code: "PE",
        txn_id: parsed_data.txn_id,
        note: parsed_data.note || "QR Payload expired"
      }
      
      # Generate error RespValQr XML
      case UpiXmlSchema.generate_resp_val_qr(resp_data) do
        {:ok, error_xml} ->
          # Send error response asynchronously to NPCI
          Task.start(fn ->
            Req.post(npci_callback_url, body: error_xml, headers: [...])
          end)
          
          # Send ACK immediately and return
          ack_xml = "<Ack reqMsgId=\"#{msg_id}\" />"
          conn |> send_resp(200, ack_xml)
      end
    end
end
```

---

#### Error Handler 2: X7 Error (Amount Exceeded)
**File**: [apps/upi_dynamic/lib/upi_dynamic/error_handlers/x7_error_handler.ex](apps/upi_dynamic/lib/upi_dynamic/error_handlers/x7_error_handler.ex)

**Typically triggered**: During ReqPay processing, but amount can be pre-validated

**Code Flow**:
```elixir
def validate_amount(parsed_data) when is_map(parsed_data) do
  amount = extract_amount_value(parsed_data)
  currency = extract_currency(parsed_data)
  
  Logger.info("X7 Validation - Amount: #{amount}, Currency: #{currency}")
  
  # Step 1: Check if amount exceeds limit
  case {amount, currency} do
    {amt, "INR"} when amt > 8000 ->
      Logger.warning("X7 Error: Amount #{amt} INR exceeds 8000 INR limit")
      {:error, :x7_amount_exceeded}
    
    {amt, _currency} when amt > 8000 ->
      Logger.warning("X7 Error: Amount #{amt} exceeds 8000 limit")
      {:error, :x7_amount_exceeded}
    
    _ ->
      Logger.info("X7 Validation: Amount is within limits")
      {:ok, :valid}
  end
end

# Then generate X7 error response:
def generate_x7_error_response(parsed_data, amount) do
  response_data = %{
    result: "FAILURE",
    err_code: "X7",
    org_amount: to_string(amount),
    sett_amount: "0.00",  # CRITICAL: Settlement is 0 for failures
    sett_currency: "INR",
    add_info: "Transaction amount #{amount} exceeds maximum allowed limit"
  }
  
  # Send X7 RespPay to NPCI
  handle_x7_error_and_send(response_data, amount)
end
```

---

#### Error Handler 3: YH Error (Merchant Processing Error)
**File**: [apps/upi_dynamic/lib/upi_dynamic/error_handlers/yh_error_handler.ex](apps/upi_dynamic/lib/upi_dynamic/error_handlers/yh_error_handler.ex)

**Code Flow**:
```elixir
def detect_and_handle_signature_error(parsed_data) do
  Logger.info("YH Validation - Checking merchant side processing...")
  
  case simulate_merchant_signature_processing(parsed_data) do
    :success ->
      Logger.info("✅ Merchant signature processing successful")
      {:ok, :processing_successful}
    
    {:error, error_type, reason} ->
      Logger.warning("YH Error: Merchant #{error_type} error - #{reason}")
      
      # Step 1: Generate YH error response
      error_response = generate_yh_error_response(parsed_data, error_type, reason)
      
      # Step 2: Send to NPCI
      case send_yh_resp_pay_to_npci(error_response, parsed_data.txn_id) do
        {:ok, _response} ->
          Logger.info("✅ YH error sent to NPCI")
          {:error, :yh_merchant_error}
        
        {:error, reason} ->
          Logger.error("❌ Failed to send YH error: #{inspect(reason)}")
          {:error, :yh_send_failed}
      end
  end
end

# YH patterns to trigger error:
defp should_simulate_signature_error?(parsed_data) do
  txn_id = Map.get(parsed_data, :txn_id, "")
  String.contains?(txn_id, "YHSIG")
end
```

---

### Phase 4: Static QR Validation Path

#### Function 7: `process_static_qr_validation(static_qr, parsed_data, qr_data)`
**File**: [apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex](apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex)

**Purpose**: Validate and return static QR details

**Code Flow**:
```elixir
defp process_static_qr_validation(static_qr, parsed_data, qr_data) do
  # Step 1: Extract merchant info from static QR record
  Logger.info("Processing static QR validation")
  
  # Step 2: Create QR validation record for audit
  qr_validation_attrs = %{
    txn_id: parsed_data.txn_id,
    msg_id: parsed_data.msg_id,
    org_id: parsed_data.org_id,
    payee_addr: static_qr.merchant_vpa,
    payee_name: static_qr.merchant_name,
    amount: static_qr.amount || "0.00",
    currency: static_qr.currency || "INR",
    ver_token: generate_ver_token(),
    status: "validated",
    validation_type: "static_qr",
    initiation_mode: "02",
    raw_xml: qr_data,
    qr_payload: parsed_data.qr_payload
  }
  
  # Step 3: Store in QR validations table
  case QRValidationService.create_validation(qr_validation_attrs) do
    {:ok, qr_validation} ->
      Logger.info("Static QR validation stored: #{qr_validation.id}")
      
      # Step 4: Prepare RespValQr response using static QR data
      response_data = %{
        org_id: get_psp_org_id(),
        msg_id: generate_message_id(),
        req_msg_id: parsed_data.msg_id,
        result: "SUCCESS",
        err_code: "00",
        txn_id: parsed_data.txn_id,
        payee_addr: static_qr.merchant_vpa,
        payee_name: static_qr.merchant_name,
        amount: static_qr.amount || "0.00",
        currency: static_qr.currency || "INR",
        ver_token: qr_validation.ver_token,
        expire_ts: static_qr.expires_at  # 1-year validity for static
      }
      
      # Step 5: Generate and return RespValQr
      case UpiXmlSchema.generate_resp_val_qr(response_data) do
        {:ok, respvalqr_xml} -> {:ok, {respvalqr_xml, qr_validation}}
        {:error, reason} -> {:error, reason}
      end
    
    {:error, reason} ->
      Logger.error("Failed to store static QR validation: #{inspect(reason)}")
      {:error, reason}
  end
end
```

---

## Call Stack Summary

```
HTTP Request (XML)
    ↓
Router → /ReqValQr/*path
    ↓
UpiController.validate_qr/2
    ├─ read_request_body(conn) → xml_body
    ├─ UpiXmlSchema.parse_req_val_qr(xml_body) → parsed_data
    ├─ [Optional] PE Expiry Check → Send error or continue
    ├─ Check Static vs Dynamic
    │
    └─ process_normal_qr_flow/3
        ├─ Generate ACK XML
        ├─ send_resp(200, ack_xml) → Return to caller
        │
        └─ Task.start(fn ->
            ├─ UpiTransactionManager.process_qr_validation(xml_body)
            │   ├─ try_static_qr_validation()
            │   │   ├─ StaticQrService.extract_qr_identifiers()
            │   │   └─ StaticQrService.get_by_tr_tn()
            │   │
            │   └─ process_regular_qr_validation()
            │       ├─ validate_merchant_qr()
            │       │   └─ Merchants.get_merchant_by_vpa()
            │       ├─ QRValidationService.create_validation()
            │       ├─ extract amounts & currencies
            │       ├─ UpiXmlSchema.generate_resp_val_qr() → XML
            │       │
            ├─ QRValidationService.store_response_xml_hash()
            │
            └─ Req.post(npci_callback_url, body: xml)
                ├─ Status 200 → Success log
                └─ Other → Error log
```

---

### Step 1: Parse Request
```
ReqValQr XML Body → UpiXmlSchema.parse_req_val_qr()
                  ↓
         parsed_data (Map with fields)
```

**Extracted Fields**:
- `msg_id`: NPCI message ID
- `txn_id`: Transaction ID
- `org_id`: Organization ID (MER101 for Mercury)
- `qr_payload`: Encoded QR string containing merchant details
- `payer_addr`, `payer_name`: Initiator information
- `qr_ts`: Timestamp when QR was created
- `timestamp`: Current transaction timestamp
- `initiation_mode`: "16" (dynamic) or "02" (static)

---

### Step 2: Early QR Expiry Check (PE Error Scenario)

**Purpose**: Detect if QR has already expired before proceeding

**Logic**:
1. Extract `QRts` (QR timestamp) from QR payload
2. Extract `QRexpire` (absolute expiry time) from QR payload if available
3. Compare with current time

**For Static QR** (has `QRexpire` field):
```
QRexpire (absolute timestamp) > Now?
  ✓ Yes → Proceed to validation
  ✗ No  → QR Expired (PE error)
```

**For Dynamic QR** (uses time window):
```
(Now - QRts) < 30 minutes (configurable)?
  ✓ Yes → Proceed to validation
  ✗ No  → QR Expired (PE error)
```

**PE Error Response**:
- Error Code: `PE` (Payment Expired)
- Result: `FAILURE`
- Async callback to NPCI with error RespValQr
- Still sends ACK immediately to caller

**Location**: [apps/upi_dynamic/lib/upi_dynamic/error_handlers/pe_error_handler.ex](apps/upi_dynamic/lib/upi_dynamic/error_handlers/pe_error_handler.ex)

**Config**: `Application.get_env(:upi_dynamic, :qr_validity_minutes, 30)`

---

### Step 3: Determine QR Type (Static vs Dynamic)

```
UpiTransactionManager.process_qr_validation()
        ↓
Is QRpayload mode=02?
  ├─ YES → Static QR Path
  │         ├─ Extract tr (transaction reference)
  │         ├─ Extract tn (transaction number)
  │         └─ Lookup in static_qr table
  │
  └─ NO → Dynamic QR Path
           └─ Extract merchant details from QR payload
```

---

## Scenario 1: Static QR Validation Path

### Triggered When
- QR initiation_mode = "02"
- QR payload contains `mode=02` parameter
- QR has tr (transaction reference) and tn (transaction number)

### Processing Flow
```
StaticQrTransactionManager.is_static_qr?(parsed_data)
                    ↓
        Extract tr and tn from QR payload
                    ↓
        StaticQrService.get_by_tr_tn(tr, tn)
                    ↓
    Is StaticQr record found in DB?
        ├─ YES → Static QR Validation Success
        │         └─ Generate RespValQr with stored merchant details
        │
        └─ NO  → Error Flow
                 └─ Generate ZQ error (QR not found)
```

### Success Response
- Merchant details from `static_qr` table
- Pre-generated verification token
- 1-year validity (from `QRexpire` field)

### Error: ZQ (QR Not Found)
- Static QR lookup failed
- Database record missing

---

## Scenario 2: Dynamic QR Validation Path

### Triggered When
- QR initiation_mode = "16"
- QR payload does NOT contain `mode=02`
- QR contains merchant VPA and other dynamic parameters

### Processing Flow

#### 2.1 Validate Merchant QR Payload

```
validate_merchant_qr(qr_payload, initiation_mode)
        ↓
Extract from QR:
  ├─ Merchant VPA (payee_addr)
  ├─ Merchant ID (mid)
  ├─ Terminal ID (tid)
  ├─ Store ID (sid)
  ├─ Amount (optional)
  ├─ Currency (optional)
  └─ Merchant Category Code
        ↓
Validate against database
```

#### 2.2 Merchant Lookup & Validation

```
Merchants.get_merchant_by_vpa(merchant_vpa)
        ↓
Found in DB?
  ├─ YES → Extract merchant info
  │         ├─ Brand name
  │         ├─ Legal name
  │         ├─ IFSC code
  │         ├─ Account number
  │         ├─ Account type
  │         ├─ FX rate
  │         ├─ Markup percentage
  │         └─ Merchant category
  │
  └─ NO  → Error: ZR (Merchant not found)
```

#### 2.3 Create QR Validation Record

```
QRValidationService.create_validation(%{
  txn_id: ...,
  msg_id: ...,
  org_id: ...,
  payee_addr: merchant_vpa,
  payee_name: merchant_brand_name,
  amount: merchant_amount,
  currency: merchant_currency,
  fx_rate: ...,
  markup_pct: ...,
  status: "validated",
  ver_token: generate_unique_token(),
  ...
})
```

**Stored Fields**:
- Transaction tracking info
- Merchant and payer details
- Amount and FX information
- Validation status and timestamp
- Raw XML request

---

## Scenario 3: Amount Validation (X7 Error)

### Triggered When
Amount exceeds threshold during RespPay (not ReqValQr)

**Note**: X7 error typically occurs in ReqPay processing, not ReqValQr. However, amount is extracted and validated:

```
Extract amount from QR payload
        ↓
For INR transactions:
  Amount > 8000 INR?
    ├─ YES → X7 Error (handled in ReqPay)
    └─ NO  → Amount valid
        
For non-INR transactions:
  Amount > 8000?
    ├─ YES → X7 Error
    └─ NO  → Amount valid
```

**Location**: [apps/upi_dynamic/lib/upi_dynamic/error_handlers/x7_error_handler.ex](apps/upi_dynamic/lib/upi_dynamic/error_handlers/x7_error_handler.ex)

---

## Scenario 4: Merchant Signature/Encryption Errors (YH Error)

### Triggered When
Merchant (acquirer side) cannot generate valid digital signature or has formatting issues

### Detection Logic
```
UpiTransactionManager processes RespPay generation
        ↓
Simulate merchant-side processing
        ↓
Check for explicit test patterns in txn_id or note:
  ├─ "YHSIG" → Signature generation failed
  ├─ "YHENC" → Encryption/decryption error
  ├─ "YHFMT" → XML formatting error
  └─ "YHCERT" → Invalid certificate/key
        ↓
If pattern detected:
  └─ Send YH error response to NPCI
        
If no pattern:
  └─ Check merchant data integrity
      └─ Validate QR payload details match DB records
```

**Location**: [apps/upi_dynamic/lib/upi_dynamic/error_handlers/yh_error_handler.ex](apps/upi_dynamic/lib/upi_dynamic/error_handlers/yh_error_handler.ex)

**YH Error Scenarios**:
- `YH01`: Signature generation failure on merchant side
- `YH02`: Encryption/decryption error during response preparation
- `YH03`: XML formatting error on acquirer side
- `YH04`: Certificate/key validation failure

---

## Scenario 5: Terminal ID Mismatch (XW Error)

### Triggered When
Terminal ID in QR payload does NOT match merchant's terminal records

```
Extract tid from QR payload
        ↓
Compare with merchant.tid in database
        ↓
Match?
  ├─ YES → Proceed
  └─ NO  → XW Error (Terminal ID mismatch)
```

**Error Response**:
- Error Code: `XW`
- Result: `FAILURE`
- Message: "Terminal ID mismatch"

---

## Scenario 6: QR Not Found (ZQ Error)

### Triggered When
- Dynamic QR: QR payload is malformed or merchant VPA doesn't exist
- Static QR: tr/tn lookup fails in static_qr table

```
ZQ Error Response:
{
  "result": "FAILURE",
  "err_code": "ZQ",
  "message": "QR code not found"
}
```

---

## Scenario 7: Merchant Not Found (ZR Error)

### Triggered When
- Merchant VPA extracted from QR payload
- No matching merchant record in database
- OR merchant is inactive/suspended

```
Merchants.get_merchant_by_vpa(vpa)
        ↓
Record found AND active?
  ├─ YES → Proceed
  └─ NO  → ZR Error (Merchant not found)
```

---

## Scenario 8: Timeout Simulation (Special Handler)

### Purpose
Test timeout handling for specific merchants

### Triggered When
Merchant VPA = `gourmet@mercury`

### Behavior
```
Special TimeoutSimulationHandler
        ↓
For ReqPay requests to gourmet@mercury:
  ├─ Normal ReqPay → Don't send RespPay (simulate timeout)
  ├─ ReqChkTxn → Don't send RespChkTxn (simulate timeout)
  └─ Reversal ReqPay → Send successful RespPay reversal
```

**Location**: [apps/upi_dynamic/lib/upi_dynamic/special_handlers/timeout_simulation_handler.ex](apps/upi_dynamic/lib/upi_dynamic/special_handlers/timeout_simulation_handler.ex)

**Uses**: Agent-based MapSet to track timeout transaction IDs

---

## Success Response Flow

### RespValQr Generation

```
Successfully validated QR
        ↓
Extract and prepare response data:
  ├─ Message IDs (req_msg_id, msg_id)
  ├─ Transaction IDs
  ├─ Payee details (merchant info)
  ├─ Amount (for Global UPI: merchant currency; Domestic: INR)
  ├─ FX information (fx_rate, base_currency, markup)
  ├─ Merchant details (mid, tid, sid, VPA)
  ├─ Account details (ifsc, account_number, account_type)
  ├─ Merchant classification (category, genre, tier)
  └─ Verification token
        ↓
UpiXmlSchema.generate_resp_val_qr(response_data)
        ↓
RespValQr XML
```

### Amount Logic (Important for Global UPI)

```
purpose = "11" (Global UPI)?
  ├─ YES → Use merchant_amount and merchant_currency
  │         Example: 100.50 USD for international payment
  │
  └─ NO → Use INR debit amount
           Example: 100.00 INR for domestic payment
```

---

## Error Response Structure

All error responses follow NPCI RespValQr schema:

```xml
<RespValQr>
  <Resp ...
    result="FAILURE"
    errCode="[ERROR_CODE]"
  />
</RespValQr>
```

### Error Codes Summary

| Code | Meaning | Scenario |
|------|---------|----------|
| `PE` | Payment Expired | QR timestamp expired |
| `X7` | Amount Exceeded | Amount > 8000 INR |
| `YH` | Merchant Processing Error | Signature/encryption/formatting issues |
| `XW` | Terminal ID Mismatch | tid doesn't match |
| `ZQ` | QR Not Found | QR payload invalid or not in DB |
| `ZR` | Merchant Not Found | Merchant VPA doesn't exist |
| `02` | Invalid Request | Generic QR validation failure |
| `05` | Invalid Merchant QR | Malformed QR payload |
| `96` | System Malfunction | XML parsing failure |

---

## Asynchronous Processing

### Task-Based Deferred Responses

```
Main Thread:
  ├─ Parse ReqValQr
  ├─ Send ACK (200 OK) to NPCI
  └─ Return control to HTTP handler

Background Task (via Task.start):
  ├─ Validate QR details
  ├─ Query database
  ├─ Generate RespValQr XML
  ├─ Store response XML hash in DB
  └─ HTTP POST RespValQr to NPCI callback
```

### NPCI Callback Endpoint

```
NPCI Endpoint Template:
https://precert.nfinite.in/iupi/RespValQr/2.0/urn:txnid:{txn_id}

Example:
https://precert.nfinite.in/iupi/RespValQr/2.0/urn:txnid:abc123def456
```

### Retry & Error Handling

```
Req.post(npci_callback_url, body: respvalqr_xml, headers: [...])
        ↓
Status 200?
  ├─ YES → Success logged
  └─ NO  → Error logged with status and response
```

---

## Amount Field Variants

ReqValQr handles multiple amount representations:

### Amount Extraction Functions

```
merchant_amount = extract_merchant_amount_from_qr(qr_payload)
  └─ bAm (Base Amount in merchant currency)

merchant_currency = extract_merchant_currency_from_qr(qr_payload)
  └─ bCurr (Base Currency)

inr_debit_amount = extract_inr_debit_amount_from_qr(qr_payload)
  └─ am (Amount in INR)
```

### RespValQr Amount Field

```
For Global UPI (purpose=11):
  amount = merchant_amount
  currency = merchant_currency

For Domestic UPI:
  amount = inr_debit_amount
  currency = "INR"
```

---

## Verification Token (Ver Token)

### Generation
```
generate_ver_token()
  └─ Unique token per QR validation
```

### Purpose
- Prevents token reuse attacks
- Links RespValQr to original ReqValQr
- Validated in subsequent ReqPay requests

### Storage
```
QR Validation Record:
  ├─ ver_token: stored
  ├─ txn_id: for reference
  └─ expires_at: calculated based on QR validity
```

---

## Database Models Involved

### 1. QR Validations Table

```elixir
%QRValidation{
  id: UUID,
  txn_id: "NPCI_TXN_ID",
  msg_id: "NPCI_MSG_ID",
  org_id: "MER101",
  payee_addr: "merchant@bank",
  payee_name: "Merchant Name",
  amount: "100.00",
  currency: "INR",
  fx_rate: "1.00",
  markup_pct: "2.50",
  ver_token: "VERIFICATION_TOKEN",
  status: "validated",
  validation_type: "global_upi" | "domestic_upi",
  corridor: "SG_IN" | "AE_IN",
  merchant_category: "5411",
  initiation_mode: "16" | "02",
  raw_xml: "...",
  response_xml_hash: "...",
  npci_request_received_at: timestamp,
  inserted_at: timestamp
}
```

### 2. Static QR Table

```elixir
%StaticQr{
  id: UUID,
  qr_id: "QR_ID",
  tr: "transaction_ref",
  tn: "transaction_num",
  partner_id: "PARTNER_ID",
  merchant_id: "MID",
  qr_string: "upi://...",
  status: "active" | "inactive",
  expires_at: timestamp,
  inserted_at: timestamp
}
```

### 3. Merchant Table

```elixir
%Merchant{
  id: UUID,
  vpa: "merchant@bank",
  mid: "MERCHANT_ID",
  tid: "TERMINAL_ID",
  sid: "STORE_ID",
  brand_name: "Merchant Display Name",
  legal_name: "Legal Entity Name",
  ifsc_code: "MERC0000001",
  account_number: "1234567890",
  account_type: "CURRENT" | "SAVINGS",
  fx_rate: "1.00",
  markup_rate: "2.50",
  merchant_category: "5411",
  status: "active" | "inactive",
  ...
}
```

---

## Request/Response Example

### Incoming ReqValQr XML

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ReqValQr xmlns="http://npci.org/upi/schema/">
  <Req
    id="TX12345678"
    ts="2026-06-12T10:30:00+05:30"
    orgId="MER101"
    msgId="MSG20260612103000"
    txnId="TXN123456789ABC"
    initiationMode="16"
    qrPayload="upi://pay?pa=merchant@hdfc&pn=GourmetStore&am=100&tr=REF001&tn=TN001"
    qrTs="2026-06-12T10:30:00+05:30"
    ...
  />
</ReqValQr>
```

### Outgoing RespValQr XML (Success)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<RespValQr xmlns="http://npci.org/upi/schema/">
  <Resp
    id="RES12345679"
    ts="2026-06-12T10:30:05+05:30"
    orgId="MER101"
    msgId="MSG20260612103005"
    reqMsgId="MSG20260612103000"
    txnId="TXN123456789ABC"
    result="SUCCESS"
    errCode="00"
  >
    <Ref
      id="REF001"
      desc="Merchant details retrieved"
    >
      <Merchant
        verToken="VER_TOKEN_123"
        amount="100.00"
        currency="INR"
        ...
      />
    </Ref>
  </Resp>
</RespValQr>
```

### Outgoing RespValQr XML (Error - PE)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<RespValQr xmlns="http://npci.org/upi/schema/">
  <Resp
    id="RES12345679"
    ts="2026-06-12T10:30:05+05:30"
    orgId="MER101"
    msgId="MSG20260612103005"
    reqMsgId="MSG20260612103000"
    txnId="TXN123456789ABC"
    result="FAILURE"
    errCode="PE"
    addInfo="QR code expired"
  />
</RespValQr>
```

---

## Configuration Parameters

### Application Config (config/config.exs, config/dev.exs, etc.)

```elixir
config :upi_dynamic,
  # QR validity window for dynamic QRs (in minutes)
  qr_validity_minutes: 30,
  
  # NPCI callback endpoint for RespValQr
  npci_qr_validation_endpoint: "https://precert.nfinite.in/iupi/RespValQr/2.0/urn:txnid:",
  
  # PSP Organization ID
  psp_org_id: "MER101",
  
  # PSP Network Institution ID
  psp_net_inst_id: "MER1010001"
```

---

## Key Takeaways

1. **Two-Phase Response**: ACK immediately, process async, callback with RespValQr
2. **Two QR Types**: Static QR (mode=02, stored) vs Dynamic QR (mode=16, computed)
3. **Early Validation**: QR expiry checked before merchant lookup
4. **Amount Logic**: Different for Global UPI (merchant currency) vs Domestic UPI (INR)
5. **Error Handling**: Specific error codes for different failure scenarios
6. **Verification Token**: Unique per validation, prevents token reuse
7. **Database Storage**: All validations logged for audit and reconciliation
8. **Merchant Lookup**: Real-time query for merchant details and FX rates
9. **Special Scenarios**: Timeout simulation, merchant errors, ID mismatches
10. **Async Processing**: Background Tasks handle heavy lifting, HTTP remains responsive

