canina/frontend/application/app/payment/verify/page.tsx
parsa aghaei 8d1b786a24
All checks were successful
Deploy Canina / deploy (push) Successful in 1m50s
chore(brand): rename all Persian occurrences of کانینا to کنینا across the project
2026-08-16 16:40:08 +03:30

292 lines
12 KiB
TypeScript
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.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}