292 lines
12 KiB
TypeScript
292 lines
12 KiB
TypeScript
"use client";
|
||
import React, { Suspense, useEffect, useState } from "react";
|
||
import { useSearchParams, useRouter } from "next/navigation";
|
||
import {
|
||
CheckCircle2,
|
||
XCircle,
|
||
Clock,
|
||
ArrowRight,
|
||
ShoppingBag,
|
||
RotateCcw,
|
||
Copy,
|
||
Receipt,
|
||
ShieldCheck,
|
||
CreditCard,
|
||
Building,
|
||
} from "lucide-react";
|
||
import { toPersian, cn } from "../../../lib/utils";
|
||
import { toast } from "sonner";
|
||
import { useCartStore } from "../../../lib/store/cartStore";
|
||
import { useUserStore } from "../../../lib/store/userStore";
|
||
import api from "../../../lib/services/api";
|
||
|
||
function VerifyContent() {
|
||
const searchParams = useSearchParams();
|
||
const router = useRouter();
|
||
|
||
const successParam = searchParams.get("success");
|
||
const trackId = searchParams.get("trackId") || "";
|
||
const orderId = searchParams.get("orderId") || "";
|
||
const refNumber = searchParams.get("refNumber") || "";
|
||
const cardNumber = searchParams.get("cardNumber") || "";
|
||
const messageParam = searchParams.get("message") || "";
|
||
const amountParam = searchParams.get("amount") || "";
|
||
const paymentType = searchParams.get("type") || "ORDER";
|
||
|
||
const isSuccess = successParam === "1";
|
||
const [orderDetails, setOrderDetails] = useState<any>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [retrying, setRetrying] = useState(false);
|
||
|
||
const { clearCart } = useCartStore();
|
||
const { fetchProfile } = useUserStore();
|
||
|
||
useEffect(() => {
|
||
if (isSuccess) {
|
||
clearCart();
|
||
fetchProfile().catch(() => {});
|
||
}
|
||
|
||
if (orderId) {
|
||
setLoading(true);
|
||
api
|
||
.get(`/orders/${orderId}`)
|
||
.then((res) => {
|
||
if (res.data) setOrderDetails(res.data);
|
||
})
|
||
.catch(() => {})
|
||
.finally(() => setLoading(false));
|
||
}
|
||
}, [isSuccess, orderId, clearCart, fetchProfile]);
|
||
|
||
const handleCopy = (text: string, title: string) => {
|
||
navigator.clipboard.writeText(text);
|
||
toast.success(`${title} با موفقیت کپی شد!`);
|
||
};
|
||
|
||
const handleRetryPayment = async () => {
|
||
if (!orderId) {
|
||
router.push("/checkout");
|
||
return;
|
||
}
|
||
|
||
setRetrying(true);
|
||
try {
|
||
const res = await api.post("/payment/zibal/initiate", { orderId });
|
||
if (res.data?.paymentUrl) {
|
||
window.location.href = res.data.paymentUrl;
|
||
} else {
|
||
toast.error("خطا در ایجاد درگاه پرداخت جدید");
|
||
}
|
||
} catch (err: any) {
|
||
const errorMsg =
|
||
err.response?.data?.message ||
|
||
err.message ||
|
||
"خطا در ایجاد اتصال مجدد به درگاه";
|
||
toast.error(errorMsg);
|
||
} finally {
|
||
setRetrying(false);
|
||
}
|
||
};
|
||
|
||
const displayAmount =
|
||
amountParam || (orderDetails?.totalAmount ? String(orderDetails.totalAmount) : "");
|
||
|
||
return (
|
||
<div className="min-h-screen bg-medical-gray-50 py-12 px-4 sm:px-6 font-vazir" dir="rtl">
|
||
<div className="max-w-2xl mx-auto">
|
||
|
||
{/* Main Status Card */}
|
||
<div
|
||
className={cn(
|
||
"bg-white rounded-[2.5rem] border p-6 sm:p-10 shadow-xl overflow-hidden relative transition-all",
|
||
isSuccess ? "border-green-200" : "border-red-200"
|
||
)}
|
||
>
|
||
{/* Top colored accent bar */}
|
||
<div
|
||
className={cn(
|
||
"absolute top-0 right-0 left-0 h-3",
|
||
isSuccess ? "bg-gradient-to-r from-emerald-500 to-green-500" : "bg-gradient-to-r from-red-500 to-rose-500"
|
||
)}
|
||
/>
|
||
|
||
<div className="text-center space-y-4 pt-2">
|
||
{/* Status Icon */}
|
||
<div
|
||
className={cn(
|
||
"w-20 h-20 sm:w-24 sm:h-24 rounded-3xl mx-auto flex items-center justify-center shadow-lg transition-transform",
|
||
isSuccess
|
||
? "bg-green-50 text-green-600 border border-green-200 shadow-green-100 scale-105"
|
||
: "bg-red-50 text-red-600 border border-red-200 shadow-red-100"
|
||
)}
|
||
>
|
||
{isSuccess ? (
|
||
<CheckCircle2 className="w-12 h-12 sm:w-14 sm:h-14" />
|
||
) : (
|
||
<XCircle className="w-12 h-12 sm:w-14 sm:h-14" />
|
||
)}
|
||
</div>
|
||
|
||
{/* Title & Status */}
|
||
<div>
|
||
<h1 className="text-2xl sm:text-3xl font-black text-medical-gray-900 italic">
|
||
{isSuccess ? "پرداخت با موفقیت انجام شد" : "پرداخت ناموفق بود"}
|
||
</h1>
|
||
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold mt-1">
|
||
{messageParam ||
|
||
(isSuccess
|
||
? "سفارش شما در سامانه ثبت و در حال آمادهسازی و ارسال است."
|
||
: "تراکنش بانکی تکمیل نشد یا توسط کاربر لغو گردید.")}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Receipt Info Box */}
|
||
<div className="mt-8 bg-medical-gray-50 rounded-2xl p-5 border border-medical-gray-200 space-y-3.5 text-xs sm:text-sm">
|
||
<div className="flex items-center justify-between pb-3 border-b border-medical-gray-200 font-bold">
|
||
<span className="text-medical-gray-500 flex items-center gap-1.5">
|
||
<Receipt className="w-4 h-4 text-canina-blue" />
|
||
درگاه پرداخت:
|
||
</span>
|
||
<span className="text-medical-gray-900 font-black">
|
||
درگاه امن زیبال (Zibal IPG)
|
||
</span>
|
||
</div>
|
||
|
||
{displayAmount && (
|
||
<div className="flex items-center justify-between pb-3 border-b border-medical-gray-200 font-bold">
|
||
<span className="text-medical-gray-500">مبلغ تراکنش:</span>
|
||
<span className="text-medical-gray-900 font-black text-base">
|
||
{toPersian(Number(displayAmount).toLocaleString("fa-IR"))} تومان
|
||
</span>
|
||
</div>
|
||
)}
|
||
|
||
{refNumber && (
|
||
<div className="flex items-center justify-between pb-3 border-b border-medical-gray-200">
|
||
<span className="text-medical-gray-500 font-bold">شماره مرجع بانکی (Ref Number):</span>
|
||
<div className="flex items-center gap-1.5 font-mono font-black text-medical-gray-900 dir-ltr">
|
||
<span>{refNumber}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleCopy(refNumber, "شماره مرجع بانکی")}
|
||
className="p-1 rounded hover:bg-white text-medical-gray-400 hover:text-canina-blue"
|
||
title="کپی"
|
||
>
|
||
<Copy className="w-3.5 h-3.5" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{trackId && (
|
||
<div className="flex items-center justify-between pb-3 border-b border-medical-gray-200">
|
||
<span className="text-medical-gray-500 font-bold">شناسه پیگیری زیبال (Track ID):</span>
|
||
<div className="flex items-center gap-1.5 font-mono font-black text-medical-gray-900 dir-ltr">
|
||
<span>{trackId}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleCopy(trackId, "شناسه پیگیری زیبال")}
|
||
className="p-1 rounded hover:bg-white text-medical-gray-400 hover:text-canina-blue"
|
||
title="کپی"
|
||
>
|
||
<Copy className="w-3.5 h-3.5" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{cardNumber && (
|
||
<div className="flex items-center justify-between pb-3 border-b border-medical-gray-200">
|
||
<span className="text-medical-gray-500 font-bold flex items-center gap-1">
|
||
<CreditCard className="w-4 h-4 text-medical-gray-400" />
|
||
شماره کارت پرداختکننده:
|
||
</span>
|
||
<span className="font-mono font-black text-medical-gray-900 dir-ltr">
|
||
{cardNumber}
|
||
</span>
|
||
</div>
|
||
)}
|
||
|
||
{orderDetails?.trackingNumber && (
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-medical-gray-500 font-bold">کد پیگیری سفارش کنینا:</span>
|
||
<span className="font-mono font-black text-canina-blue text-sm dir-ltr">
|
||
{orderDetails.trackingNumber}
|
||
</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
<div className="mt-8 space-y-3">
|
||
{isSuccess ? (
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
{orderId && (
|
||
<button
|
||
onClick={() => router.push(`/checkout/success/${orderId}`)}
|
||
className="w-full bg-canina-blue text-white py-4 rounded-2xl font-black text-sm hover:bg-blue-700 transition-all flex items-center justify-center gap-2 shadow-lg shadow-canina-blue/20"
|
||
>
|
||
<ShoppingBag className="w-4 h-4" />
|
||
<span>مشاهده جزئیات سفارش</span>
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={() => router.push("/shop")}
|
||
className="w-full bg-medical-gray-100 text-medical-gray-900 py-4 rounded-2xl font-black text-sm hover:bg-medical-gray-200 transition-all flex items-center justify-center gap-2"
|
||
>
|
||
<span>بازگشت به فروشگاه</span>
|
||
<ArrowRight className="w-4 h-4 rotate-180" />
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
<button
|
||
onClick={handleRetryPayment}
|
||
disabled={retrying}
|
||
className="w-full bg-canina-blue text-white py-4 rounded-2xl font-black text-sm hover:bg-blue-700 transition-all flex items-center justify-center gap-2 shadow-lg shadow-canina-blue/20 disabled:opacity-50"
|
||
>
|
||
<RotateCcw className={cn("w-4 h-4", retrying && "animate-spin")} />
|
||
<span>{retrying ? "در حال انتقال به درگاه..." : "تلاش مجدد برای پرداخت"}</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => router.push("/checkout")}
|
||
className="w-full bg-medical-gray-100 text-medical-gray-900 py-4 rounded-2xl font-black text-sm hover:bg-medical-gray-200 transition-all flex items-center justify-center gap-2"
|
||
>
|
||
<span>بازگشت به سبد خرید</span>
|
||
<ArrowRight className="w-4 h-4 rotate-180" />
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Security Notice */}
|
||
<div className="mt-8 pt-6 border-t border-medical-gray-100 flex items-center justify-center gap-2 text-xs font-bold text-medical-gray-400">
|
||
<ShieldCheck className="w-4 h-4 text-canina-blue" />
|
||
<span>تراکنش تحت پروتکل رمزنگاری امن شاپرک و درگاه پرداخت زیبال</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function PaymentVerifyPage() {
|
||
return (
|
||
<Suspense
|
||
fallback={
|
||
<div className="min-h-screen bg-medical-gray-50 flex items-center justify-center p-6" dir="rtl">
|
||
<div className="text-center space-y-4">
|
||
<div className="w-12 h-12 border-4 border-canina-blue/20 border-t-canina-blue rounded-full animate-spin mx-auto" />
|
||
<p className="font-bold text-sm text-medical-gray-600">در حال دریافت و اعتبارسنجی تراکنش پرداخت زیبال...</p>
|
||
</div>
|
||
</div>
|
||
}
|
||
>
|
||
<VerifyContent />
|
||
</Suspense>
|
||
);
|
||
}
|