cover/Elixir.DaProductAppWeb.ReqPayLive.html

1
:-(
defmodule DaProductAppWeb.ReqPayLive do
2 @moduledoc """
3 LiveView for ReqPay (Payment Request) monitoring and analytics.
4
5 Features:
6 - Read-only payment request monitoring
7 - Search and filter capabilities
8 - Real-time payment status updates
9 - Payment request details and history
10 - Payment analytics and metrics
11 """
12 use DaProductAppWeb, :live_view
13
14 alias DaProductApp.Repo
15 alias DaProductApp.Transactions.ReqPay
16 alias DaProductApp.Accounts
17 import Ecto.Query
18
19 @impl true
20
:-(
def mount(_params, session, socket) do
21
:-(
case get_current_user(session) do
22
:-(
nil ->
23 {:ok, redirect(socket, to: ~p"/login")}
24
25 user ->
26
:-(
if has_access?(user) do
27
:-(
if connected?(socket) do
28 # Subscribe to payment request events for real-time updates
29
:-(
Phoenix.PubSub.subscribe(DaProductApp.PubSub, "req_pay_updates")
30 end
31
32
:-(
socket =
33 socket
34 |> assign(:current_user, user)
35 |> assign(:page_title, "Payment Requests (ReqPay)")
36 |> assign(:current_page, :req_pay)
37 |> assign(:show_details_modal, false)
38 |> assign(:selected_payment, nil)
39 |> assign(:search_term, "")
40 |> assign(:status_filter, "all")
41 |> assign(:validation_type_filter, "all")
42 |> assign(:payment_type_filter, "all")
43 |> assign(:date_from, nil)
44 |> assign(:date_to, nil)
45 |> assign(:page, 1)
46 |> assign(:per_page, 25)
47 |> load_payments()
48 |> load_statistics()
49
50 {:ok, socket}
51 else
52 {:ok,
53 socket
54 |> put_flash(:error, "Access denied")
55 |> redirect(to: ~p"/login")}
56 end
57 end
58 end
59
60 @impl true
61
:-(
def handle_params(params, _url, socket) do
62
:-(
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
63 end
64
65 defp apply_action(socket, :index, _params) do
66 socket
67 |> assign(:page_title, "Payment Requests (ReqPay)")
68
:-(
|> assign(:show_details_modal, false)
69 end
70
71 defp apply_action(socket, :show, %{"id" => id}) do
72
:-(
payment = Repo.get!(ReqPay, id) |> Repo.preload([:transaction, :partner, :merchant])
73
74 socket
75 |> assign(:page_title, "Payment Request Details")
76 |> assign(:show_details_modal, true)
77
:-(
|> assign(:selected_payment, payment)
78 end
79
80 @impl true
81
:-(
def handle_event("search", %{"search_term" => term}, socket) do
82
:-(
socket =
83 socket
84 |> assign(:search_term, term)
85 |> assign(:page, 1)
86 |> load_payments()
87
88 {:noreply, socket}
89 end
90
91
:-(
def handle_event("filter_status", %{"status" => status}, socket) do
92
:-(
socket =
93 socket
94 |> assign(:status_filter, status)
95 |> assign(:page, 1)
96 |> load_payments()
97
98 {:noreply, socket}
99 end
100
101
:-(
def handle_event("filter_validation_type", %{"type" => type}, socket) do
102
:-(
socket =
103 socket
104 |> assign(:validation_type_filter, type)
105 |> assign(:page, 1)
106 |> load_payments()
107
108 {:noreply, socket}
109 end
110
111
:-(
def handle_event("filter_payment_type", %{"type" => type}, socket) do
112
:-(
socket =
113 socket
114 |> assign(:payment_type_filter, type)
115 |> assign(:page, 1)
116 |> load_payments()
117
118 {:noreply, socket}
119 end
120
121
:-(
def handle_event("filter_date", %{"date_from" => from, "date_to" => to}, socket) do
122
:-(
date_from = if from != "", do: Date.from_iso8601!(from), else: nil
123
:-(
date_to = if to != "", do: Date.from_iso8601!(to), else: nil
124
125
:-(
socket =
126 socket
127 |> assign(:date_from, date_from)
128 |> assign(:date_to, date_to)
129 |> assign(:page, 1)
130 |> load_payments()
131
132 {:noreply, socket}
133 end
134
135
:-(
def handle_event("filter_date_range", %{"date_from" => from, "date_to" => to}, socket) do
136
:-(
date_from = if from != "", do: Date.from_iso8601!(from), else: nil
137
:-(
date_to = if to != "", do: Date.from_iso8601!(to), else: nil
138
139
:-(
socket =
140 socket
141 |> assign(:date_from, date_from)
142 |> assign(:date_to, date_to)
143 |> assign(:page, 1)
144 |> load_payments()
145
146 {:noreply, socket}
147 end
148
149
:-(
def handle_event("clear_filters", _params, socket) do
150
:-(
socket =
151 socket
152 |> assign(:search_term, "")
153 |> assign(:status_filter, "all")
154 |> assign(:validation_type_filter, "all")
155 |> assign(:payment_type_filter, "all")
156 |> assign(:date_from, nil)
157 |> assign(:date_to, nil)
158 |> assign(:page, 1)
159 |> load_payments()
160
161 {:noreply, socket}
162 end
163
164
:-(
def handle_event("paginate", %{"page" => page}, socket) do
165
:-(
page = String.to_integer(page)
166
167
:-(
socket =
168 socket
169 |> assign(:page, page)
170 |> load_payments()
171
172 {:noreply, socket}
173 end
174
175
:-(
def handle_event("show_details", %{"id" => id}, socket) do
176
:-(
payment = Repo.get!(ReqPay, id) |> Repo.preload([:transaction, :partner, :merchant])
177
178
:-(
socket =
179 socket
180 |> assign(:show_details_modal, true)
181 |> assign(:selected_payment, payment)
182
183 {:noreply, socket}
184 end
185
186
:-(
def handle_event("close_details", _params, socket) do
187
:-(
socket =
188 socket
189 |> assign(:show_details_modal, false)
190 |> assign(:selected_payment, nil)
191
192 {:noreply, socket}
193 end
194
195
:-(
def handle_event("refresh", _params, socket) do
196
:-(
socket =
197 socket
198 |> load_payments()
199 |> load_statistics()
200 |> put_flash(:info, "Data refreshed successfully")
201
202 {:noreply, socket}
203 end
204
205
:-(
def handle_event("set_per_page", %{"per_page" => per_page_str}, socket) do
206
:-(
per_page = case Integer.parse(per_page_str) do
207 {v, _} when v > 0 ->
208 # optional cap to avoid very large page sizes
209
:-(
if v > 1000, do: 1000, else: v
210
:-(
_ -> socket.assigns.per_page || 25
211 end
212
213
:-(
socket =
214 socket
215 |> assign(:per_page, per_page)
216 |> assign(:page, 1)
217 |> load_payments()
218
219 {:noreply, socket}
220 end
221
222 # Private helper functions
223
224 # Helper functions for authentication and access control
225
:-(
defp get_current_user(session) do
226
:-(
case session do
227 %{"user_id" => user_id} when is_binary(user_id) or is_integer(user_id) ->
228
:-(
Accounts.get_user!(user_id)
229
:-(
_ ->
230 nil
231 end
232 rescue
233
:-(
_ -> nil
234 end
235
236 # Access control (can be extended with real permission checks)
237
:-(
defp has_access?(_user), do: true
238
239 defp load_payments(socket) do
240 %{
241 search_term: search_term,
242 status_filter: status_filter,
243 validation_type_filter: validation_type_filter,
244 payment_type_filter: payment_type_filter,
245 date_from: date_from,
246 date_to: date_to,
247 page: page,
248 per_page: per_page
249
:-(
} = socket.assigns
250
251
:-(
query = build_query(search_term, status_filter, validation_type_filter, payment_type_filter, date_from, date_to)
252
253
:-(
total_count = Repo.aggregate(query, :count, :id)
254
:-(
total_pages = max(1, ceil(total_count / per_page))
255
256
:-(
payments =
257 query
258 |> order_by([p], desc: p.inserted_at)
259 |> limit(^per_page)
260 |> offset(^((page - 1) * per_page))
261
:-(
|> preload([:transaction, :partner, :merchant])
262 |> Repo.all()
263
264
:-(
assign(socket,
265 payments: payments,
266 total_count: total_count,
267 total_pages: total_pages,
268 current_page: page
269 )
270 end
271
272 defp load_statistics(socket) do
273 # Get basic statistics
274
:-(
total_payments = Repo.aggregate(ReqPay, :count, :id)
275
276
:-(
pending_count = Repo.aggregate(
277 from(p in ReqPay, where: p.status == "PENDING"),
278 :count,
279 :id
280 )
281
282
:-(
processed_count = Repo.aggregate(
283 from(p in ReqPay, where: p.status == "PROCESSED"),
284 :count,
285 :id
286 )
287
288
:-(
failed_count = Repo.aggregate(
289 from(p in ReqPay, where: p.status == "FAILED"),
290 :count,
291 :id
292 )
293
294
:-(
success_rate = if total_payments > 0 do
295
:-(
Float.round(processed_count / total_payments * 100, 2)
296 else
297 0.0
298 end
299
300
:-(
assign(socket,
301 total_payments: total_payments,
302 pending_count: pending_count,
303 processed_count: processed_count,
304 failed_count: failed_count,
305 success_rate: success_rate
306 )
307 end
308
309 defp build_query(search_term, status_filter, validation_type_filter, payment_type_filter, date_from, date_to) do
310
:-(
query = from(p in ReqPay)
311
312
:-(
query =
313 if search_term != "" do
314
:-(
search_pattern = "%#{search_term}%"
315
:-(
from p in query,
316 where: ilike(p.msg_id, ^search_pattern) or
317 ilike(p.txn_id, ^search_pattern) or
318 ilike(p.payer_addr, ^search_pattern) or
319 ilike(p.payee_addr, ^search_pattern)
320 else
321
:-(
query
322 end
323
324
:-(
query =
325 if status_filter != "all" do
326
:-(
from p in query, where: p.status == ^status_filter
327 else
328
:-(
query
329 end
330
331
:-(
query =
332 if validation_type_filter != "all" do
333
:-(
from p in query, where: p.validation_type == ^validation_type_filter
334 else
335
:-(
query
336 end
337
338
:-(
query =
339 if payment_type_filter != "all" do
340
:-(
from p in query, where: p.payment_type == ^payment_type_filter
341 else
342
:-(
query
343 end
344
345
:-(
query =
346 if date_from do
347
:-(
from p in query, where: fragment("DATE(?)", p.inserted_at) >= ^date_from
348 else
349
:-(
query
350 end
351
352
:-(
if date_to do
353
:-(
from p in query, where: fragment("DATE(?)", p.inserted_at) <= ^date_to
354 else
355
:-(
query
356 end
357 end
358
359
:-(
defp format_amount(amount) when is_nil(amount), do: "N/A"
360
:-(
defp format_amount(amount), do: "₹ #{Decimal.to_string(amount)}"
361
362
:-(
defp format_datetime(nil), do: "N/A"
363 defp format_datetime(datetime) do
364 datetime
365 |> DateTime.shift_zone!("Asia/Kolkata")
366
:-(
|> Calendar.strftime("%d %b %Y, %I:%M %p IST")
367 end
368
369
:-(
defp status_badge_class("PENDING"), do: "badge-warning"
370
:-(
defp status_badge_class("PROCESSED"), do: "badge-success"
371
:-(
defp status_badge_class("FAILED"), do: "badge-error"
372
:-(
defp status_badge_class(_), do: "badge-ghost"
373
374
:-(
defp payment_status_badge_class("PENDING"), do: "badge-warning"
375
:-(
defp payment_status_badge_class("SUCCESS"), do: "badge-success"
376
:-(
defp payment_status_badge_class("FAILURE"), do: "badge-error"
377
:-(
defp payment_status_badge_class("EXPIRED"), do: "badge-error"
378
:-(
defp payment_status_badge_class(_), do: "badge-ghost"
379
380
:-(
defp validation_type_badge_class("DOMESTIC"), do: "badge-info"
381
:-(
defp validation_type_badge_class("INTERNATIONAL"), do: "badge-accent"
382
:-(
defp validation_type_badge_class(_), do: "badge-ghost"
383 end
Line Hits Source