Files
nobat724_front/app/payment/[uuid]/page.js
T
hamedandClaude Opus 4.8 59d29cd7c7 fix(appointment): correct payment flow and account-info prefill
- Booking response is double-nested: read appointment uuid/expires_at from
  res.data.data so the payment countdown and gateway redirect actually fire.
- Payment result page (/payment/[uuid]): unwrap res.data.data, use real
  backend fields (amount_rials, gateway, created_at, type) and statuses
  (pending/success/failed/canceled/refunded); the "pay" button now re-initiates
  via postAppointmentPayment instead of building a URL on the API origin.
- Add /payment/result interstitial that reads payment_uuid from the gateway
  callback and forwards to /payment/[uuid].
- Prefill account info correctly in the booking form: name from profile.label,
  insurances from *_id, gender as string, national_code editable unless approved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 10:06:56 +03:30

239 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { request } from "@/services/response";
import Cookies from "js-cookie";
import Layout from "@/components/layout";
export default function PaymentDetailsPage() {
const params = useParams();
const router = useRouter();
const [payment, setPayment] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [paying, setPaying] = useState(false);
const handleRetryPayment = async () => {
if (!payment?.appointment_uuid) return;
setPaying(true);
try {
const res = await request.postAppointmentPayment({
appointment_uuid: payment.appointment_uuid,
gateway: payment.gateway || "mellat",
frontend_address: `${window.location.origin}/payment/result`,
});
const redirectUrl = res?.data?.redirect_url;
if (redirectUrl) {
window.location.href = redirectUrl;
} else {
setPaying(false);
}
} catch {
setPaying(false);
}
};
useEffect(() => {
// بررسی لاگین بودن کاربر
const userInfo = Cookies.get("userInfo");
if (!userInfo) {
router.push("/login");
return;
}
const fetchPaymentDetails = async () => {
try {
setLoading(true);
const response = await request.getPayment(params.uuid);
setPayment(response?.data?.data ?? null);
} catch (err) {
setError("خطا در دریافت اطلاعات پرداخت");
console.error("Payment fetch error:", err);
} finally {
setLoading(false);
}
};
if (params.uuid) {
fetchPaymentDetails();
}
}, [params.uuid, router]);
const getStatusLabel = (status) => {
const statusMap = {
pending: "در انتظار پرداخت",
success: "پرداخت موفق",
failed: "پرداخت ناموفق",
refunded: "مسترد شده",
};
return statusMap[status] || status;
};
const getStatusColor = (status) => {
const colorMap = {
pending: "text-yellow-600 bg-yellow-50",
success: "text-green-600 bg-green-50",
failed: "text-red-600 bg-red-50",
refunded: "text-gray-600 bg-gray-50",
};
return colorMap[status] || "text-gray-600 bg-gray-50";
};
const getPaymentMethodLabel = (method) => {
const methodMap = {
mellat: "بانک ملت",
sep: "سامان (سپ)",
mock: "درگاه آزمایشی",
};
return methodMap[method] || method;
};
const formatDate = (timestamp) => {
const date = new Date(parseInt(timestamp) * 1000);
return new Intl.DateTimeFormat("fa-IR", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(date);
};
const formatAmount = (rials) => {
return new Intl.NumberFormat("fa-IR").format(Math.round(parseInt(rials) / 10));
};
if (loading) {
return (
<Layout>
<div className="flex items-center justify-center min-h-screen">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-[#5559CE] border-t-transparent rounded-full animate-spin"></div>
<p className="text-[#3B3B3B] text-[16px]">در حال بارگذاری...</p>
</div>
</div>
</Layout>
);
}
if (error) {
return (
<Layout>
<div className="flex items-center justify-center min-h-screen">
<div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-md">
<p className="text-red-600 text-center">{error}</p>
</div>
</div>
</Layout>
);
}
if (!payment) {
return null;
}
return (
<Layout>
<div className="container mx-auto px-4 py-8 max-w-4xl mt-20 md:mt-24">
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-6 md:p-8">
<h1 className="text-2xl md:text-3xl font-bold text-[#3B3B3B] mb-6">
جزئیات پرداخت
</h1>
<div className="space-y-4">
{/* وضعیت پرداخت */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">وضعیت:</span>
<span
className={`px-4 py-2 rounded-full font-bold ${getStatusColor(
payment.status
)}`}
>
{getStatusLabel(payment.status)}
</span>
</div>
{/* مبلغ */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">مبلغ:</span>
<span className="text-[#3B3B3B] font-bold text-lg">
{formatAmount(payment.amount_rials)} تومان
</span>
</div>
{/* روش پرداخت */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">روش پرداخت:</span>
<span className="text-[#3B3B3B] font-bold">
{getPaymentMethodLabel(payment.gateway)}
</span>
</div>
{/* شناسه پرداخت */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">شناسه پرداخت:</span>
<span className="text-[#3B3B3B] font-mono text-sm">
{payment.uuid}
</span>
</div>
{/* شماره مرجع */}
{payment.reference_id && (
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">شماره مرجع:</span>
<span className="text-[#3B3B3B] font-bold">
{payment.reference_id}
</span>
</div>
)}
{/* تاریخ ایجاد */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">تاریخ ایجاد:</span>
<span className="text-[#3B3B3B]">
{formatDate(payment.created_at)}
</span>
</div>
{/* نوع سرویس */}
<div className="flex justify-between items-center py-3">
<span className="text-[#616161] font-medium">نوع سرویس:</span>
<span className="text-[#3B3B3B] font-bold">
{payment.type === "appointment" ? "رزرو نوبت" : payment.type}
</span>
</div>
</div>
{/* دکمه‌ها */}
<div className="mt-8 flex gap-4">
{payment.status === "pending" ? (
<>
<button
onClick={handleRetryPayment}
disabled={paying || !payment.appointment_uuid}
className="flex-1 bg-[#5559CE] hover:bg-[#4448b3] disabled:opacity-60 text-white font-bold py-3 px-6 rounded-lg transition-colors"
>
{paying ? "در حال انتقال به درگاه..." : "پرداخت"}
</button>
<button
onClick={() => window.location.reload()}
className="flex-1 bg-gray-200 hover:bg-gray-300 text-[#3B3B3B] font-bold py-3 px-6 rounded-lg transition-colors"
>
بروزرسانی وضعیت
</button>
</>
) : null}
<button
onClick={() => router.push("/dashboard?sidebar=2")}
className="flex-1 bg-[#5559CE] hover:bg-[#4448b3] text-white font-bold py-3 px-6 rounded-lg transition-colors"
>
بازگشت به تراکنشها
</button>
</div>
</div>
</div>
</Layout>
);
}