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
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
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}
2478 npci_risk_score: "0",
2479 add_info: error_message,
2480 expire_ts: DateTime.utc_now() |> DateTime.add(300, :second) |> DateTime.to_iso8601()
2481 }
2482
2483 case UpiXmlSchema.generate_resp_pay(error_response_data) do
2484 {:ok, xml_response} ->
2485 conn
2486 |> put_resp_content_type("application/xml")
2487 |> send_resp(500, xml_response)
2488
1defmodule UpiStatic.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
1defmodule UpiStatic.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
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)
1defmodule UpiStatic.Endpoint do
2 use Phoenix.Endpoint, otp_app: :upi_static
3
4 @session_options [
5 store: :cookie,
6 key: "_upi_static_key",
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)
1defmodule UpiStatic.Endpoint do
2 use Phoenix.Endpoint, otp_app: :upi_static
3
4 @session_options [
5 store: :cookie,
6 key: "_upi_static_key",
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
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
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})
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
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
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)