defmodule DaProductAppWeb.DeviceInitiateController do use DaProductAppWeb, :controller require Logger alias DaProductAppWeb.Validators.QRParamsValidator alias DaProductAppWeb.Services.EventLogger alias DaProductAppWeb.Handlers.ResponseHandler @default_provider "alipay" @spec initiate(any(), any()) :: Plug.Conn.t() def initiate(conn, params) do amount = parse_amount(params["amount"]) refrence_id = params["externalRefNumber"] txnType = params["txnType"] case txnType do "QR" -> handle_qr_txn(conn, params, amount, refrence_id) "card" -> handle_card_txn(conn, params, amount, refrence_id) _ -> ResponseHandler.error_response( conn, :bad_request, "MERCURY_0000400", "Invalid transaction type" ) end end defp handle_qr_txn(conn, params, amount, refrence_id) do with {:ok, _} <- EventLogger.store_event_and_log( "Initiate QR Request", params, amount, nil, refrence_id, nil, params["username"] ), :ok <- QRParamsValidator.validate_params(params), {:ok, ref} <- display_qr_code(conn, params, amount) do ResponseHandler.send_success(conn, ref) else {:error, :event_log} -> ResponseHandler.error_response( conn, :internal_server_error, "MERCURY_0000503", "Failed to log initiate event" ) {:error, msg} when is_binary(msg) -> ResponseHandler.error_response(conn, :bad_request, "MERCURY_4000001", msg) {:error, :provider_key} -> ResponseHandler.error_response( conn, :bad_request, "MERCURY_6000001", "Failed to retrieve provider key." ) {:error, :event_log, code, msg} -> ResponseHandler.error_response(conn, :internal_server_error, code, msg) {:error, :unsupported_provider} -> ResponseHandler.error_response( conn, :bad_request, "MERCURY_0000000", "unsupported_provider" ) {:error, :qr_generation, reason} -> ResponseHandler.error_response( conn, :internal_server_error, "MERCURY_0000501", "Unexpected error: #{inspect(reason)}" ) {:error, :not_found, code, msg} -> ResponseHandler.error_response(conn, :service_unavailable, code, msg) {:error, :publish, reason} -> ResponseHandler.error_response( conn, :service_unavailable, "MERCURY_0000623", "Publish failed: #{inspect(reason)}" ) {:error, :provider_not_associated_with_device_id} -> ResponseHandler.error_response( conn, :provider_not_associated_with_device_id, "MERCURY_0000404", "Device not found" ) {:error, :invalid_amount} -> ResponseHandler.error_response( conn, :bad_request, "MERCURY_0000402", "Invalid amount provided" ) {:error, :invalid_params} -> ResponseHandler.error_response( conn, :bad_request, "MERCURY_0000400", "Invalid parameters provided" ) {:error, :provider_not_found} -> ResponseHandler.error_response(conn, :not_found, "MERCURY_0000405", "Provider not found") _ -> ResponseHandler.error_response( conn, :internal_server_error, "MERCURY_5000000", "Unknown error." ) end end defp handle_card_txn(conn, params, amount, refrence_id) do # TODO: Implement card transaction handling logic Logger.info("Processing card transaction for reference: #{refrence_id}") with {:ok, _} <- EventLogger.store_event_and_log( "Initiate card device Request", params, amount, nil, refrence_id, nil, params["username"] ), :ok <- QRParamsValidator.validate_params(params), {:ok, ref} <- do_handle_card_txn(conn, params, amount) do ResponseHandler.send_success(conn, ref) else {:error, msg} when is_binary(msg) -> ResponseHandler.error_response(conn, :bad_request, "MERCURY_4000001", msg) {:error, _} -> ResponseHandler.error_response(conn, :internal_server_error, "MERCURY_5000000", "Failed to log event") end end # Handles card transactions. defp do_handle_card_txn(conn, params, amount) do Logger.info(""" Handling card transaction: - Reference: #{params["externalRefNumber"]} - Amount: #{amount} - Params: #{inspect(params)} """) device_id = params["pushTo"]["deviceId"] amount = parse_amount(params["amount"]) case DaProductApp.DeviceRegistry.is_online?(device_id) do online_status -> # Log device status event {:ok, _} = EventLogger.store_event_and_log( "Response From Device Status", %{ device_id: device_id, status: if(online_status, do: "online", else: "offline"), ts: System.system_time(:second) }, amount, nil, params["externalRefNumber"], nil, params["username"] ) case online_status do true -> topic = "cmd/qr-device/#{device_id}" payload = Jason.encode!(%{amount: amount, ts: System.system_time(:second)}) Logger.info("Publish payload #{device_id}: #{inspect(payload)}") {:ok, _} = EventLogger.store_event_and_log( "Request For Publish Device QR", %{ topic: topic, qos: 1, amount: amount, ts: System.system_time(:second), device_id: device_id }, amount, nil, params["externalRefNumber"], nil, params["username"] ) case Tortoise.publish("phoenix_client_node_devteammiddlelayer", topic, payload, qos: 1) do {:ok, ref} -> # Log successful publish response with reference {:ok, _} = EventLogger.store_event_and_log( "Response From Publish Device QR", %{ device_id: device_id, amount: amount, topic: topic, status: "success", ts: System.system_time(:second), ref: inspect(ref), error: nil }, amount, nil, params["externalRefNumber"], nil, params["username"] ) #conn #|> put_status(200) #|> json(%{status: "sent", device: device_id, ref: inspect(ref)}) Logger.info("Publish successful to #{device_id} with ref: #{inspect(ref)}") # ResponseHandler.send_success(conn, ref: inspect(ref)) # Create new response array new_resp = %{ "transaction_refid" => params["externalRefNumber"], "mRefId" => nil } ResponseHandler.send_success(conn, new_resp) {:error, reason} -> # Log failed publish response {:ok, _} = EventLogger.store_event_and_log( "Response From Publish Device QR", %{ device_id: device_id, amount: amount, topic: topic, status: "failed", ts: System.system_time(:second), error: inspect(reason) }, amount, nil, params["externalRefNumber"], nil, params["username"] ) Logger.error("Publish failed to #{device_id}: #{inspect(reason)}") #conn #|> put_status(503) #|> json(%{error: "publish_failed"}) ResponseHandler.error_response(conn, :internal_server_error, "MERCURY_503", "publish_failed") end false -> conn |> put_status(404) |> json(%{error: "device_offline"}) end end # TODO: Add actual card transaction processing logic here # For now, return a mock reference {:ok, "CARD_" <> params["externalRefNumber"]} end # Generates QR code, checks device online, logs event, publishes QR, sends response. defp display_qr_code(conn, params, amount) do device_id = params["pushTo"]["deviceId"] # Add event logging for QR Code request {:ok, _} = EventLogger.store_event_and_log( "Request For Get QRCode", params, amount, nil, params["externalRefNumber"], nil, params["username"] ) # Prepare parameters for QRMiddleLayerController middle_layer_params = %{ "transaction_refid" => params["externalRefNumber"], "merchantId" => params["appKey"], "merchantName" => params["accountLabel"], "amount" => amount, "additionalData" => %{ "customerMobileNumber" => params["customerMobileNumber"], "externalRefNumber2" => params["externalRefNumber2"], "externalRefNumber4" => params["externalRefNumber4"], "externalRefNumbers" => params["externalRefNumbers"] }, "deviceId" => device_id } Logger.info("QR Request - Starting process for device: #{device_id}") Logger.info("QR Request - Parameters: #{inspect(middle_layer_params)}") # Call QRMiddleLayerController's processTransaction response = case HTTPoison.post( "http://demo.ctrmv.com:4004/api/processTransaction", # Replace with your actual API endpoint Jason.encode!(middle_layer_params), [{"Content-Type", "application/json"}] ) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> case Jason.decode(body) do {:ok, decoded_response} -> {:ok, decoded_response["data"]} {:error, decode_error} -> Logger.error("Failed to decode response: #{inspect(decode_error)}") {:error, :decode_error, "Failed to decode response"} end {:ok, %HTTPoison.Response{status_code: 404, body: body}} -> case Jason.decode(body) do {:ok, %{"code" => code, "message" => message}} -> {:error, :not_found, code, message} _ -> {:error, :not_found, "MERCURY_0000404", "Resource not found"} end {:ok, %HTTPoison.Response{status_code: status_code, body: body}} -> case Jason.decode(body) do {:ok, %{"code" => code, "message" => message}} -> Logger.error("Error response with code #{code}: #{message}") {:error, :unexpected_response, code, message} _ -> Logger.error("Unexpected status code #{status_code}: #{inspect(body)}") {:error, :unexpected_response, "MERCURY_#{status_code}", "Unexpected response from server"} end {:error, %HTTPoison.Error{reason: reason}} -> Logger.error("HTTP request failed: #{inspect(reason)}") {:error, :http_error, "Failed to make HTTP request"} end Logger.info( "Raw QR Middleware Response: #{inspect(response, pretty: true, limit: :infinity)}" ) case response do {:ok, qr_response} -> Logger.info(""" QR Success Response: - Device ID: #{device_id} - QR Code: #{inspect(qr_response["qrId"])} - Full Response: #{inspect(qr_response, pretty: true)} """) is_device_online = DaProductApp.DeviceRegistry.is_online?(device_id) Logger.info( "Device Status - #{device_id} is #{if is_device_online, do: "online", else: "offline"}" ) qrResponse = qr_response merchantId = qrResponse["mRefId"] transaction_id = qrResponse["transaction_id"] Logger.info("Merchant ID: #{merchantId}") with true <- is_device_online, {:ok, _} <- EventLogger.store_event_and_log( "Response Received from QR Display", qrResponse, amount, merchantId, params["externalRefNumber"], transaction_id, params["username"] ), {:ok, _} <- DaProductApp.Activity.CustomEventsLog.update_transaction_id( params["externalRefNumber"], transaction_id ), {:ok, ref} <- publish_qr(device_id, amount, qr_response, params) do Logger.info("QR Published - Successfully published QR code with ref: #{inspect(ref)}") {:ok, ref} else false -> Logger.error("Device offline error for device_id: #{device_id}") {:error, :device_offline} {:error, reason} -> Logger.error("Publish error: #{inspect(reason)}") {:error, :publish, reason} end {:error, :not_found, code, message} -> Logger.error(""" QR Error - Not Found: - Device ID: #{device_id} - Error Code: #{code} - Message: #{message} """) {:error, :not_found, code, message} {:error, reason, message} -> Logger.error(""" QR Error: - Device ID: #{device_id} - Error Type: #{reason} - Message: #{message} """) {:error, :qr_generation, message} unexpected -> Logger.error(""" QR Unexpected Error: - Device ID: #{device_id} - Response: #{inspect(unexpected)} """) {:error, :qr_generation, "Unexpected response"} end end # Publishes QR code payload to MQTT topic. defp publish_qr(device_id, amount, qr_response, params) do # Add event logging before publishing {:ok, _} = EventLogger.store_event_and_log( "Request For Publish QR Code In MQTT", %{ device_id: device_id, amount: amount, qrcode: qr_response["qrId"], topic: "/ota/pFppbioOCKlo5c8E/#{device_id}/update", ts: System.system_time(:second) }, amount, qr_response["mRefId"], params["externalRefNumber"], qr_response["transaction_id"], params["username"] ) Logger.info("Publishing QR code to MQTT topic: cmd/qr-device/#{device_id}") topic = "/ota/pFppbioOCKlo5c8E/#{device_id}/update" #payload = Jason.encode!(%{amount: amount, qrcode: qr_response["qrCodeId"], ts: System.system_time(:second)}) datetime = NaiveDateTime.utc_now() |> NaiveDateTime.to_iso8601() |> String.replace(~r/[-:T]/, "") |> String.slice(0..13) # Trim milliseconds to match expected format #request_id = :crypto.strong_rand_bytes(8) |> Base.encode16() request_id = qr_response["transaction_id"] payload = Jason.encode!(%{ money: amount, order_sn: qr_response["qrCodeId"], datetime: datetime, ctime: System.system_time(:second), request_id: request_id }) Logger.info(""" MQTT Publish Details: - Topic: #{topic} - Payload: #{payload} """) case Tortoise.publish("phoenix_client_node_devteammiddlelayer", topic, payload, qos: 1) do {:ok, ref} = response -> # Log successful publish response {:ok, _} = EventLogger.store_event_and_log( "Response From Publish QR Code IN MQTT", %{ device_id: device_id, amount: amount, qrcode: qr_response["qrId"], topic: topic, ref: inspect(ref), status: "sent", ts: System.system_time(:second), error: nil, reason: nil }, amount, qr_response["mRefId"], params["externalRefNumber"], qr_response["transaction_id"], params["username"] ) Logger.info(""" MQTT Publish Success: - Reference: #{inspect(ref)} - Device ID: #{device_id} - Response: #{inspect(response)} """) {:ok, qr_response} {:error, reason} = error -> # Log failed publish response {:ok, _} = EventLogger.store_event_and_log( "Response From Publish QR Code IN MQTT", %{ device_id: device_id, amount: amount, qrcode: qr_response["qrId"], topic: topic, ref: nil, status: "unsent", ts: System.system_time(:second), error: inspect(error), reason: inspect(reason) }, amount, qr_response["mRefId"], params["externalRefNumber"], qr_response["transaction_id"], params["username"] ) Logger.error(""" MQTT Publish Error: - Device ID: #{device_id} - Error: #{inspect(error)} - Reason: #{inspect(reason)} """) {:error, reason} end end # Parses amount as integer or float, raises if invalid. defp parse_amount(amount) when is_integer(amount) or is_float(amount), do: amount defp parse_amount(amount) when is_binary(amount) do case Integer.parse(amount) do {int, ""} -> int _ -> case Float.parse(amount) do {flt, ""} -> flt _ -> raise "invalid_amount" end end end defp parse_amount(_), do: raise("invalid_amount") end