CHANGE LOG (context.txt) Record of what was changed, why, and where. Newest entries at the bottom. Line numbers are as of the date of the entry and will drift as the files change; grep the tag instead: grep -rn "\[RespHbt Txn.refId\]" apps/ ========================================================================================== 2026-11-04 ========================================================================================== Change 1: RespHbt Txn.refId is now generated (35 chars) instead of echoed from ReqHbt ------------------------------------------------------------------------------------------ Trigger NFinite precert, test case MT_125 (ReqHbt), Execution Result: Failed. Response code: U07:TXN.REFID IS REQUIRED OR MUST BE 35 ALPHANUMERIC CHARACTERS Root cause NPCI's ReqHbt carries a 12-digit refId (e.g. 017629623413). Our RespHbt echoed it back in . NPCI's validator requires refId to be 35 alphanumeric characters, so it answered our RespHbt with an Ack containing err="U07:...". (Earlier TSD v16 work, patch 05, had switched this field from a generated REF value to an echo of the request. That is what exposed this.) What changed ref_id in the RespHbt data map now comes from NpciFormat.new_msg_id() ("MER" + 32 lowercase hex chars = 35 alphanumeric). refUrl is still echoed from the request. The old line (ref_id: parsed_req_data.ref_id) was removed, not kept as an "# OLD:" comment (per user request). Files and lines 1. apps/upi_dynamic/lib/upi_dynamic/upi_transaction_manager.ex lines 2598-2600, inside process_heartbeat_response_async/1 (RespHbt data map) (NpciFormat was already aliased at line 14) 2. apps/upi_static/lib/upi_static/static_qr_transaction_manager.ex lines 2683-2685, inside the RespHbt data map (uses the full name UpiCore.NpciFormat.new_msg_id()) Same defect, fixed the same way. Not exercised against NPCI. Note: the comment above it at line 2680 still says "echo the ReqHbt refId and refUrl"; that is now only true for refUrl. Docs updated docs/TSD_V16_COMPLIANCE_BRANCH_CONTEXT.md, section "Patch 05: RespHbt", sub-heading "Follow-up (2026-11-04 ...)" (line ~200). Verification Dynamic app re-run against precert at ~15:19 IST: RespHbt sent with refId="MER7081eea43e20a44b294c08469d27d938" (35 chars) NPCI Ack for it: with no err attribute (the previous run's Ack had the U07 error). Not verified: static-QR heartbeat path; unit tests (resp_hbt_test.exs) were not re-run after the change and may still assert the echoed refId. Notes - This is a deliberate exception to the branch rule "echo, never invent": only RespHbt refId is generated. Do not revert it to an echo; NPCI rejects the 12-digit value. - Log line "Sending RespHbt to NPCI endpoint: .../urn:txnid:" is printed before the txn id is appended. It is log noise only; the request goes to the correct URL. - No git commands were run and no patch file was created for this change. Change 2 (TEMPORARY): hide /upi-monitoring/metrics request logs ------------------------------------------------------------------------------------------ Why Prometheus scrapes GET /upi-monitoring/metrics every 10s; its log lines ("GET ...", "Processing with UpiWeb.MetricsController.index/2", "Sent 200 in Nms") were interleaved with the NPCI heartbeat/precert logs and made them hard to read while testing. What 1. apps/upi_web/lib/upi_web/endpoint.ex Plug.Telemetry now has log: {__MODULE__, :request_log_level, []} (~line 73) and a new request_log_level/1 at the end of the module returns false for /upi-monitoring/metrics, :info for everything else. 2. apps/upi_web/lib/upi_web/router.ex get "/metrics", MetricsController, :index, log: false (~line 31) hides the "Processing with ..." debug line. Metrics are still served; only the logging is off. Other routes log as before. To revert (both tagged "TEMP (2026-11-04)"): remove the log: option and request_log_level/1 from endpoint.ex, and the ", log: false" from router.ex. Restart the app to apply. Change 3 (EXPERIMENT): restore inside in RespValQr ------------------------------------------------------------------------------------------ Trigger NFinite precert, MT_VALQRDEP (ReqValQr): NPCI Ack for our RespValQr says "PRECERT:QuoteCurr is not present or empty". Cause not confirmed. What we know - No quoteCurr attribute exists in Payee.Fx in TSD v16 or the older TSD (attributes: baseAmount, baseCurr, active, Fx, Mkup, lastModifiedTs; NIPL fills active/Fx/Mkup). - Branch origin/feature/umbrella-3app-split (reported as working) differs from this branch in RespValQr by having inside (between Identifier and Name). Amount logic is identical on both. - Caveat: the older code comment in upi_xml_schema.ex says precert gave the same QuoteCurr error for the mixed-case layout, so this may not fix it. What changed apps/upi_core/lib/upi_core/upi_xml_schema.ex, generate_resp_val_qr/1 - new merchant_fx_tag (built from QR bAm / bCurr, empty if missing), before the xml heredoc - "#{merchant_fx_tag}" added between identifier_tag and inside Tag: # [RespValQr Merchant.Fx] ADDED (EXPERIMENT 2026-11-04). Not TSD v16 Table 2 compliant by design. To undo: delete the merchant_fx_tag block and its line in the heredoc. Result (2026-11-04 16:00 IST): re-ran MT_VALQRDEP with inside ; NPCI Ack was still "PRECERT:QuoteCurr is not present or empty". So the Merchant/Fx structure is not the cause. REVERTED the same day (code is back to TSD v16 Table 2 layout). Change 4 (EXPERIMENT): global dynamic QR (mode 16) generated with cu = bCurr and no am ------------------------------------------------------------------------------------------ Date: 2026-11-05 Why MT_VALQRDEP still fails with "PRECERT:QuoteCurr is not present or empty" after FX layout changes (Change 3, reverted). NPCI's own precert sample QR has cu=EUR and bCurr=EUR (same currency) and no am; the TSD v16 sample QRs have neither cu nor am. Our QR had cu=INR with bCurr=AED and am=120.66. Boss's suggestion: make cu the merchant currency. Cause NOT confirmed. What changed apps/upi_core/lib/upi_core/transactions/upi_international_service.ex (QR string builder, ~line 973-1030) 1. after determine_amount_fields/3: am_field is set to nil when initiation_mode == "16" 2. cu param: mode "16" -> "cu=#{qr_data.foreign_currency}" (e.g. AED); other modes unchanged (mode != "02" -> cu=INR, mode "02" -> none) Tag: # [QR am / cu] CHANGED (EXPERIMENT 2026-11-05) Only mode 16 is affected. The QR is signed after it is built, so the signature stays valid. QRs generated before this change still carry cu=INR and am; generate a NEW QR to test. Risks to watch - am is only read from the QR in upi_transaction_manager.ex:174 (RespValQr, non-global branch; falls back to "0.00"). - Special handlers (ACC_IP_05/08/10) and pe_error_handler read cu= as settlement currency (INR); re-check those test cases if this is kept. To undo: delete the am_field line (item 1) and the mode "16" clause of the cu cond (item 2). Result (2026-11-05 12:52 IST): fresh QR had cu=AED, no am (confirmed in the ReqValQr logs); NPCI Ack was still "PRECERT:QuoteCurr is not present or empty", so this alone does not fix it. Briefly reverted, then RE-APPLIED and KEPT at the user's request (cu=AED, no am for mode 16 stay in the code).