defmodule DaProductApp.Transactions do @moduledoc """ Context module for transaction-related queries. """ alias DaProductApp.Repo @doc """ Fetch the last transaction for given merchant and terminal. Returns {:ok, map} | {:not_found, reason} | {:error, reason} """ def fetch_last_for(merchant_id, terminal_id, opts \\ []) do serial = Keyword.get(opts, :serial_number) serial = if is_binary(serial), do: (s = String.trim(serial); if(s == "", do: nil, else: s)), else: serial base_sql = """ SELECT t.total_amount, t.currency_code, t.masked_card_no, t.encrypted_pan, t.acquirer_reference_no, t.reference_no, t.approval_code, t.s_tid_stan, t.s_tid_invoiceno, t.s_mid, t.s_tid, t.created_dateTime, t.response_code, t.response_message FROM pos_transaction t """ {sql, params} = if serial do { base_sql <> " JOIN pos_terminal ter ON ter.terminalid = t.s_tid WHERE t.s_mid = ? AND t.s_tid = ? AND ter.serial_number = ?", [merchant_id, terminal_id, serial] } else {base_sql <> " WHERE t.s_mid = ? AND t.s_tid = ?", [merchant_id, terminal_id]} end sql = sql <> " ORDER BY t.created_dateTime DESC LIMIT 1" case Repo.query(sql, params) do {:ok, res} when is_map(res) -> cols = Map.get(res, :columns) || Map.get(res, "columns") || [] rows = Map.get(res, :rows) || Map.get(res, "rows") || [] case rows do [] -> {:not_found, "no rows"} [row | _] -> map = Enum.zip(cols, row) |> Enum.into(%{}) {:ok, map_pos_transaction_row(map)} end {:error, reason} -> {:error, reason} end end defp map_pos_transaction_row(row_map) do masked = Map.get(row_map, "masked_card_no") || Map.get(row_map, :masked_card_no) pan_encrypted = Map.get(row_map, "encrypted_pan") || Map.get(row_map, :encrypted_pan) card_masked = cond do is_binary(masked) and String.trim(masked) != "" -> masked is_binary(pan_encrypted) -> mask_pan(pan_encrypted) true -> nil end inserted_at = Map.get(row_map, "created_dateTime") || Map.get(row_map, :created_dateTime) {date, time} = format_timestamp(inserted_at) amount = Map.get(row_map, "total_amount") || Map.get(row_map, :total_amount) || Map.get(row_map, "amount") currency = Map.get(row_map, "currency_code") || Map.get(row_map, :currency_code) || Map.get(row_map, "currency") rrn = Map.get(row_map, "acquirer_reference_no") || Map.get(row_map, :acquirer_reference_no) || Map.get(row_map, "reference_no") trace = Map.get(row_map, "s_tid_stan") || Map.get(row_map, :s_tid_stan) || Map.get(row_map, "s_tid_invoiceno") || Map.get(row_map, :s_tid_invoiceno) %{ "amount" => to_string(amount || ""), "currency" => currency || "", "cardNumberMasked" => card_masked, "date" => date, "time" => time, "rrn" => rrn, "authCode" => Map.get(row_map, "approval_code") || Map.get(row_map, :approval_code), "traceNo" => trace, "merchantId" => Map.get(row_map, "s_mid") || Map.get(row_map, :s_mid), "terminalId" => Map.get(row_map, "s_tid") || Map.get(row_map, :s_tid) } end defp mask_pan(nil), do: nil defp mask_pan(pan) when is_binary(pan) do len = String.length(pan) if len <= 4 do pan else last4 = String.slice(pan, -4..-1) "****" <> last4 end end defp format_timestamp(nil), do: {nil, nil} defp format_timestamp(%NaiveDateTime{} = dt) do {Date.to_iso8601(NaiveDateTime.to_date(dt)), NaiveDateTime.to_time(dt) |> Time.to_string() |> String.slice(0, 8)} end defp format_timestamp(%DateTime{} = dt) do dt = DateTime.to_naive(dt) {Date.to_iso8601(NaiveDateTime.to_date(dt)), NaiveDateTime.to_time(dt) |> Time.to_string() |> String.slice(0, 8)} end defp format_timestamp(dt) when is_binary(dt) do case String.split(dt, " ") do [d, t | _] -> {d, String.slice(t, 0, 8)} _ -> {dt, nil} end end defp format_timestamp(dt), do: {to_string(dt), nil} end