ArgumentError at POST //ReqValQr/2.0/urn:txnid:ROUTECHECK
XML signing key path is not configured: set the XML_SIGNING_PRIV_KEY_PATH environment variable (config :upi_core, :xml_signing_priv_key_path)
35
36  @doc """
37  Reads the RSA private key (PEM) from `path`. Raises with a clear message when the path is not configured.
38  """
39  def load_private_key!(path) when path in [nil, ""] do
40    raise ArgumentError,
41          "XML signing key path is not configured: set the XML_SIGNING_PRIV_KEY_PATH environment variable " <>
42            "(config :upi_core, :xml_signing_priv_key_path)"
43  end
44
45  def load_private_key!(path) when is_binary(path) do
UpiCore.XmlDsig.load_private_key!/1 Docs upi_core
1030# OLD:       </KeyInfo>
1031# OLD:   </Signature>
1032# OLD:   """
1033# OLD: end
1034defp generate_signature_block(xml_body) do
1035  priv_key = UpiCore.XmlDsig.load_private_key!(Application.get_env(:upi_core, :xml_signing_priv_key_path))
1036
1037  # the document as it is after the enveloped-signature transform: generate_resp_pay builds
1038  # xml_body <> "\n" <> signature_block <> "\n</upi:RespPay>", so with the Signature removed it is this
1039  unsigned_document = xml_body <> "\n" <> "\n</upi:RespPay>"
1040
UpiCore.UpiXmlSchema.generate_signature_block/1 upi_core
955  <Resp reqMsgId="#{NpciFormat.xml_attr(resp_req_msg_id)}" result="#{NpciFormat.xml_attr(result_final)}"#{if result_final != "SUCCESS", do: attr_always.("errCode", err_code), else: ""}>
956    <Ref type="PAYEE" seqNum="1"#{attr_always.("addr", payee_addr)}#{attr_always.("code", req_val.(:payee_code))} orgAmount="#{NpciFormat.xml_attr(org_amount)}"#{attr_always.("respCode", ref_resp_code)}#{reversal_resp_code_attr}#{attr_always.("regName", Map.get(request_source, :payee_name) || Map.get(response_data, :reg_name))}#{attr_always.("IFSC", Map.get(request_source, :payee_ifsc) || Map.get(response_data, :ifsc))}#{attr_always.("acNum", Map.get(request_source, :payee_ac_num) || Map.get(response_data, :ac_num))}#{attr_always.("accType", Map.get(request_source, :payee_ac_type) || Map.get(response_data, :acc_type))} approvalNum="#{approval_num_truncated}" settAmount="#{NpciFormat.xml_attr(sett_amount_final)}" settCurrency="INR" />
957  </Resp>
958"""
959
960    signature_block = generate_signature_block(xml_body)
961
962    # Append Signature block and closing tag to form complete XML
963    xml = xml_body <> "\n" <> signature_block <> "\n</upi:RespPay>"
964
965    {:ok, xml}
UpiCore.UpiXmlSchema.generate_resp_pay/1 Docs upi_core
2525      npci_risk_score: "0",
2526      add_info: error_message,
2527      expire_ts: DateTime.utc_now() |> DateTime.add(300, :second) |> DateTime.to_iso8601()
2528    }
2529
2530    case UpiXmlSchema.generate_resp_pay(error_response_data) do
2531      {:ok, xml_response} ->
2532        conn
2533        |> put_resp_content_type("application/xml")
2534        |> send_resp(500, xml_response)
2535
UpiDynamic.Api.V1.UpiController.send_error_response/3 upi_dynamic
1defmodule UpiDynamic.Api.V1.UpiController do
2  @moduledoc """
3  Complete UPI Controller implementing all 8 UPI APIs as per NPCI specification.
4  Handles both NPCI→PSP requests and PSP→NPCI responses with full XML compliance.
5  """
6
UpiDynamic.Api.V1.UpiController.action/2 upi_dynamic
1defmodule UpiDynamic.Api.V1.UpiController do
2  @moduledoc """
3  Complete UPI Controller implementing all 8 UPI APIs as per NPCI specification.
4  Handles both NPCI→PSP requests and PSP→NPCI responses with full XML compliance.
5  """
6
UpiDynamic.Api.V1.UpiController.phoenix_controller_pipeline/2 upi_dynamic
411        :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
412        halted_conn
413
414      %Plug.Conn{} = piped_conn ->
415        try do
416          plug.call(piped_conn, plug.init(opts))
417        else
418          conn ->
419            measurements = %{duration: System.monotonic_time() - start}
420            metadata = %{metadata | conn: conn}
421            :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
Phoenix.Router.__call__/5 phoenix
1defmodule UpiDynamic.Endpoint do
2  use Phoenix.Endpoint, otp_app: :upi_dynamic
3
4  @session_options [
5    store: :cookie,
6    key: "_upi_dynamic_key",
UpiDynamic.Endpoint.plug_builder_call/2 upi_dynamic
150          case conn do
151            %Plug.Conn{path_info: ["__plug__", "debugger", "action"], method: "POST"} ->
152              Plug.Debugger.run_action(conn)
153
154            %Plug.Conn{} ->
155              super(conn, opts)
156          end
157        rescue
158          e in Plug.Conn.WrapperError ->
159            %{conn: conn, kind: kind, reason: reason, stack: stack} = e
160            Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)
UpiDynamic.Endpoint."call (overridable 3)"/2 upi_dynamic
1defmodule UpiDynamic.Endpoint do
2  use Phoenix.Endpoint, otp_app: :upi_dynamic
3
4  @session_options [
5    store: :cookie,
6    key: "_upi_dynamic_key",
UpiDynamic.Endpoint.call/2 upi_dynamic
17
18  def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19
20  defp do_call(conn, endpoint, opts, retry?) do
21    try do
22      endpoint.call(conn, opts)
23    rescue
24      exception in [UndefinedFunctionError] ->
25        case exception do
26          %UndefinedFunctionError{module: ^endpoint} when retry? ->
27            # Sync with the code reloader and retry once
Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4 phoenix
126    end
127  end
128
129  @spec call_plug!(Plug.Conn.t(), plug_def()) :: Plug.Conn.t() | no_return()
130  defp call_plug!(%Plug.Conn{} = conn, {plug, plug_opts}) when is_atom(plug) do
131    case plug.call(conn, plug_opts) do
132      %Plug.Conn{} = conn -> conn
133      other -> raise("Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(other)}")
134    end
135  end
136
Bandit.Pipeline.call_plug!/2 bandit
37      conn = build_conn!(transport, method, request_target, headers, conn_data, opts)
38      span = Bandit.Telemetry.start_span(:request, measurements, Map.put(metadata, :conn, conn))
39
40      try do
41        conn
42        |> call_plug!(plug)
43        |> maybe_upgrade!()
44        |> case do
45          {:no_upgrade, conn} ->
46            %Plug.Conn{adapter: {_mod, adapter}} = conn = commit_response!(conn)
47            Bandit.Telemetry.stop_span(span, adapter.metrics, %{conn: conn})
Bandit.Pipeline.run/5 bandit
8  def handle_data(data, socket, state) do
9    transport = %Bandit.HTTP1.Socket{socket: socket, buffer: data, opts: state.opts}
10    connection_span = ThousandIsland.Socket.telemetry_span(socket)
11    conn_data = Bandit.SocketHelpers.conn_data(socket)
12
13    case Bandit.Pipeline.run(transport, state.plug, connection_span, conn_data, state.opts) do
14      {:ok, transport} -> maybe_keepalive(transport, state)
15      {:error, _reason} -> {:close, state}
16      {:upgrade, _transport, :websocket, opts} -> do_websocket_upgrade(opts, state)
17    end
18  end
Bandit.HTTP1.Handler.handle_data/3 bandit
13    |> handle_bandit_continuation(socket)
14  end
15
16  @impl ThousandIsland.Handler
17  def handle_data(data, socket, %{handler_module: handler_module} = state) do
18    handler_module.handle_data(data, socket, state)
19    |> handle_bandit_continuation(socket)
20  end
21
22  @impl ThousandIsland.Handler
23  def handle_shutdown(socket, %{handler_module: handler_module} = state) do
Bandit.DelegatingHandler.handle_data/3 bandit
3  # Delegates all implementation of the ThousandIsland.Handler behaviour
4  # to an implementation specified in state. Allows for clean separation
5  # between protocol implementations & friction free protocol selection &
6  # upgrades.
7
8  use ThousandIsland.Handler
9
10  @impl ThousandIsland.Handler
11  def handle_connection(socket, %{handler_module: handler_module} = state) do
12    handler_module.handle_connection(socket, state)
13    |> handle_bandit_continuation(socket)
Bandit.DelegatingHandler.handle_continue/2 bandit
No code available.
:gen_server.try_handle_continue/3 stdlib
No code available.
:gen_server.loop/7 stdlib
No code available.
:proc_lib.init_p_do_apply/3 stdlib
Params
path
["2.0", "urn:txnid:ROUTECHECK"]
Request info
URI:
http://mercurypay.aritic.app:80//ReqValQr/2.0/urn:txnid:ROUTECHECK
Query string:
Headers
accept
*/*
connection
Keep-Alive
content-length
4
content-type
application/xml
host
mercurypay.aritic.app
user-agent
curl/8.5.0
x-forwarded-for
78.47.213.209
x-forwarded-host
mercurypay.aritic.app
x-forwarded-port
443
x-forwarded-proto
https
x-forwarded-server
mercurypay.aritic.app