Add payment module, BFF proxy, hub/dashboard/ops/PSP/merchant screens wired to real APIs, and mark payment-frontend complete in project status. Co-authored-by: Cursor <cursoragent@cursor.com>
84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
"use client";
|
||
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import Link from "next/link";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { paymentApi } from "@/modules/payment/services/payment-api";
|
||
import { PaymentPageLoader, PaymentPageError, PaymentEmptyState } from "@/modules/payment/pages/shared";
|
||
import { PageHeader, Card, CardContent } from "@/components/ds";
|
||
import { PaymentStatusChip } from "@/modules/payment/components/PaymentStatusChip";
|
||
|
||
export const PaymentCallbacks = function PaymentCallbacksPage() {
|
||
const { tenantId } = useTenantId();
|
||
|
||
const q = useQuery({
|
||
queryKey: ["payment", tenantId, "callbacks"],
|
||
queryFn: async () => {
|
||
const [audit, requests] = await Promise.all([
|
||
paymentApi.audit.list(tenantId!),
|
||
paymentApi.requests.list(tenantId!),
|
||
]);
|
||
const callbackAudit = audit.filter(
|
||
(a) =>
|
||
String(a.entity_type ?? "").includes("Callback") ||
|
||
String(a.action ?? "").toLowerCase().includes("callback")
|
||
);
|
||
const awaiting = requests.filter((r) => r.status === "redirect_issued");
|
||
return { callbackAudit, awaiting };
|
||
},
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
if (!tenantId || q.isLoading) return <PaymentPageLoader />;
|
||
if (q.error) return <PaymentPageError error={q.error} onRetry={() => q.refetch()} />;
|
||
|
||
const { callbackAudit, awaiting } = q.data!;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<PageHeader
|
||
title="کالبکها"
|
||
description="رویدادهای callback از audit و درخواستهای در انتظار بازگشت از PSP."
|
||
/>
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<h3 className="mb-3 text-sm font-semibold">در انتظار callback ({awaiting.length})</h3>
|
||
{awaiting.length === 0 ? (
|
||
<PaymentEmptyState title="درخواست redirect_issued نیست" />
|
||
) : (
|
||
<ul className="space-y-2 text-sm">
|
||
{awaiting.map((r) => (
|
||
<li key={String(r.id)} className="flex items-center justify-between gap-2">
|
||
<Link href={`/payment/requests/${r.id}`} className="text-teal-600 hover:underline">
|
||
{String(r.amount_minor)} {String(r.currency)}
|
||
</Link>
|
||
<PaymentStatusChip status={String(r.status)} />
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<h3 className="mb-3 text-sm font-semibold">لاگ حسابرسی callback</h3>
|
||
{callbackAudit.length === 0 ? (
|
||
<p className="text-sm text-[var(--muted)]">رویداد callback در audit ثبت نشده.</p>
|
||
) : (
|
||
<ul className="space-y-2 text-xs text-[var(--muted)]">
|
||
{callbackAudit.map((a) => (
|
||
<li key={String(a.id)}>
|
||
{String(a.action)} — {String(a.entity_type)} —{" "}
|
||
{a.created_at ? new Date(String(a.created_at)).toLocaleString("fa-IR") : "—"}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default PaymentCallbacks;
|