"use client"; import React, { useState, useEffect } from "react"; import { motion } from "motion/react"; import { ChevronRight, ChevronLeft, CreditCard, Truck, ShieldCheck, Wallet, Clock, ArrowRight, Heart, PlusCircle, Copy, Phone, AlertCircle } from "lucide-react"; import { toPersian, cn } from "../lib/utils"; import { PRODUCTS } from "../lib/data/products"; import { useCartStore } from "../lib/store/cartStore"; import { usePetStore } from "../lib/store/usePetStore"; import { useUserStore } from "../lib/store/userStore"; import { useSettingsStore } from "../lib/store/settingsStore"; import TopUpModal from "./TopUpModal"; import AddressModal from "./AddressModal"; import SearchableSelect from "./SearchableSelect"; import { IRAN_PROVINCES, PROVINCE_CITIES } from "../lib/data/provinces"; import { toast } from "sonner"; import { useRouter } from 'next/navigation'; import api from "../lib/services/api"; export default function CheckoutPage() { const router = useRouter(); const { items, getTotal, getSubtotal, isSubscribed, charityDonation, setCharityDonation, addOrder, clearCart } = useCartStore(); const { pets, activePetId, getActivePet, updatePet } = usePetStore(); const { profile, isLoggedIn } = useUserStore(); const [loading, setLoading] = useState(false); const [useRoundUp, setUseRoundUp] = useState(false); const [paymentMethod, setPaymentMethod] = useState('card'); const [showTopUpModal, setShowTopUpModal] = useState(false); const [showAddressModal, setShowAddressModal] = useState(false); const isRefillEnabled = useSettingsStore((state) => state.getText('REFILL_SUBSCRIPTION_ENABLED', 'false') === 'true'); const refillPercent = Number(useSettingsStore((state) => state.getText('REFILL_REWARD_PERCENT', '5'))) || 5; const [selectedPetId, setSelectedPetId] = useState(activePetId || (pets[0]?.id ?? null)); const [selectedAddressId, setSelectedAddressId] = useState(''); // Custom manual address state if guest or not using stored addresses const [manualName, setManualName] = useState(''); const [manualPhone, setManualPhone] = useState(''); const [manualProvince, setManualProvince] = useState(''); const [manualCity, setManualCity] = useState(''); const [manualZipCode, setManualZipCode] = useState(''); const [manualAddress, setManualAddress] = useState(''); const handleCopy = (text: string, title: string) => { navigator.clipboard.writeText(text); toast.success(`${title} با موفقیت کپی شد!`); }; useEffect(() => { Promise.resolve().then(() => { // Set default selected address if logged in if (isLoggedIn && profile.addresses && profile.addresses.length > 0) { const def = profile.addresses.find(a => a.isDefault) || profile.addresses[0]; setSelectedAddressId(def.id); } else if (isLoggedIn && profile.firstName) { // Pre-fill profile info for manual fields setManualName(`${profile.firstName} ${profile.lastName}`.trim()); setManualPhone(profile.mobile || ''); } }); }, [isLoggedIn, profile]); const roundStep = Number(useSettingsStore((state) => state.getText('CHARITY_ROUND_STEP', '10000'))) || 10000; const shippingFeeSetting = useSettingsStore((state) => state.getText('shipping_fee', '0')); const baseShippingFee = Number(shippingFeeSetting) || 0; const freeThreshold = Number(useSettingsStore((state) => state.getText('free_shipping_threshold', '2000000'))) || 0; const subtotal = getSubtotal(); const shippingFee = (freeThreshold > 0 && subtotal >= freeThreshold) ? 0 : baseShippingFee; const isCardToCardEnabled = useSettingsStore((state) => state.getBoolean('PAY_GATEWAY_CARD_ENABLE', true) && state.getBoolean('cardToCardEnabled', true) && state.getBoolean('cart_to_cart_enabled', true) && state.getBoolean('payment_card_to_card_enabled', true) ); const remainder = subtotal % roundStep; const roundUpDiff = remainder === 0 ? roundStep : roundStep - remainder; const handleCharityToggle = () => { if (!useRoundUp) { setCharityDonation(roundUpDiff); setUseRoundUp(true); } else { setCharityDonation(0); setUseRoundUp(false); } }; const handleFinalize = async () => { const totalAmount = getTotal() + shippingFee; const activePet = getActivePet(); // 1. Validate shipping address const selectedAddr = profile.addresses?.find(a => a.id === selectedAddressId); let shippingAddressStr = ''; if (selectedAddr) { shippingAddressStr = `${selectedAddr.province}، ${selectedAddr.city}، ${selectedAddr.detail}${selectedAddr.zipCode ? ` (کد پستی: ${selectedAddr.zipCode})` : ''} (گیرنده: ${selectedAddr.receptorName} - ${toPersian(selectedAddr.phone)})`; } else if (manualAddress) { const provCity = [manualProvince, manualCity].filter(Boolean).join('، '); shippingAddressStr = `${provCity ? provCity + '، ' : ''}${manualAddress}${manualZipCode ? ` (کد پستی: ${manualZipCode})` : ''}${manualName ? ` (گیرنده: ${manualName}${manualPhone ? ` - ${toPersian(manualPhone)}` : ''})` : ''}`; } if (!selectedAddressId && !manualAddress) { toast.error("لطفاً یک آدرس برای ارسال سفارش انتخاب کرده یا آدرس جدید وارد کنید."); return; } // 2. Validate wallet balance if payment method is wallet if (paymentMethod === 'wallet' && (profile.walletBalance || 0) < totalAmount) { const neededAmount = totalAmount - (profile.walletBalance || 0); toast.error( `موجودی کیف پول شما (${(profile.walletBalance || 0).toLocaleString('fa-IR')} تومان) کمتر از مبلغ سفارش (${totalAmount.toLocaleString('fa-IR')} تومان) است. کادر افزایش موجودی (${neededAmount.toLocaleString('fa-IR')} تومان) باز شد.`, ); setShowTopUpModal(true); return; } setLoading(true); try { // 3. Register the order in the backend const orderId = await addOrder({ items: [...items], total: totalAmount, charityDonation: charityDonation, paymentMethod: paymentMethod, isRefill: isSubscribed, petId: selectedPetId || undefined, shippingAddress: shippingAddressStr }); // 4. Handle Online Payment via Zibal IPG if (paymentMethod === 'online') { try { const res = await api.post('/payment/zibal/initiate', { orderId }); if (res.data?.paymentUrl) { toast.success("در حال انتقال به درگاه پرداخت زیبال..."); window.location.href = res.data.paymentUrl; return; } else { toast.error("خطا در ایجاد تراکنش پرداخت زیبال"); } } catch (gatewayErr: unknown) { const gErr = gatewayErr as { response?: { data?: { message?: string } }; message?: string }; const errMsg = gErr.response?.data?.message || gErr.message || "خطا در اتصال به درگاه پرداخت زیبال"; toast.error(errMsg); router.push(`/checkout/success/${orderId}`); return; } } // 5. Re-fetch user profile from backend to sync PostgreSQL wallet balance and charity donation total const { fetchProfile } = useUserStore.getState(); await fetchProfile(); // 6. Update pet consumptions and charity contributions for active pet const targetPet = activePet || usePetStore.getState().pets[0]; if (targetPet) { const newConsumptions = [...(targetPet.consumptions || [])]; items.forEach(item => { const existing = newConsumptions.find(c => c.productId === item.product.id); if (existing) { existing.remaining += item.product.packageSize * item.quantity; existing.packageSize = item.product.packageSize; } else { newConsumptions.push({ productId: item.product.id, packageSize: item.product.packageSize, remaining: item.product.packageSize * item.quantity }); } }); updatePet(targetPet.id, { consumptions: newConsumptions }); } clearCart(); toast.success("سفارش شما با موفقیت ثبت شد!"); router.push(`/checkout/success/${orderId}`); } catch (err: unknown) { // Re-fetch profile to guarantee local state is always identical to PostgreSQL useUserStore.getState().fetchProfile().catch(() => {}); if (!(err as Record)?._toastShown) { const errorObj = err as { response?: { data?: { message?: string; details?: Array<{ message?: string }> } }; message?: string }; const backendMessage = errorObj.response?.data?.message || errorObj.response?.data?.details?.[0]?.message || errorObj.message; if (backendMessage && !backendMessage.includes("Request failed")) { toast.error(`خطا در ثبت سفارش: ${backendMessage}`); } } } finally { setLoading(false); } }; const getText = useSettingsStore(state => state.getText); const isCatalogMode = getText('catalog_mode', 'false') === 'true'; const isCatalogDisableCheckout = getText('catalog_disable_checkout', 'false') === 'true'; const isCheckoutDisabled = isCatalogMode || isCatalogDisableCheckout; if (isCheckoutDisabled) { return (

امکان ثبت سفارش آنلاین موقتاً غیرفعال است

سامانه در حال حاضر در حالت کاتالوگ قرار دارد. جهت استعلام قیمت و دریافت مشاوره تخصصی می‌توانید با کارشناسان ما تماس بگیرید.

تماس با پشتیبانی ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})
); } if (items.length === 0) { return (

سبد خرید شما خالی است

); } return (
{/* Navigation & Breadcrumbs */}
/ router.push('/shop')}>سبد خرید / تسویه‌حساب و پرداخت
پرداخت ۱۰۰٪ امن با پروتکل SSL
{/* Main Content (8 cols) */}
{/* Step 1: Shipping */}
۱

اطلاعات تحویل و گیرنده

{isLoggedIn && ( )}
{isLoggedIn && profile.addresses && profile.addresses.length > 0 ? (
{profile.addresses.map((addr) => (
setSelectedAddressId(addr.id)} className={cn( "p-3 rounded-xl border transition-all flex items-start gap-2.5 cursor-pointer", selectedAddressId === addr.id ? "border-canina-blue bg-canina-blue/5 shadow-xs" : "border-medical-gray-200 hover:border-medical-gray-300" )} > setSelectedAddressId(addr.id)} className="mt-1 shrink-0 cursor-pointer" />
{addr.title} {addr.isDefault && ( پیش‌فرض )}

{addr.province}، {addr.city}، {addr.detail}

گیرنده: {addr.receptorName} ({toPersian(addr.phone)})

))}
) : (
setManualName(e.target.value)} className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none" placeholder="مثلاً: پارسا آقایی" />
setManualPhone(e.target.value.replace(/[^0-9]/g, ''))} className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none dir-ltr text-right" placeholder="۰۹۱۲XXXXXXX" />
{ const cities = PROVINCE_CITIES[prov] || []; setManualProvince(prov); setManualCity(cities[0] || ''); }} />
{manualProvince && PROVINCE_CITIES[manualProvince] ? ( setManualCity(c)} /> ) : ( setManualCity(e.target.value)} className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none" placeholder="نام شهر..." /> )}
setManualAddress(e.target.value)} className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none" placeholder="خیابان، کوچه، پلاک، واحد..." />
setManualZipCode(e.target.value.replace(/[^0-9]/g, ''))} className="w-full bg-medical-gray-50/70 border border-medical-gray-200 rounded-xl py-2 px-3 text-xs focus:ring-1 focus:ring-canina-blue outline-none dir-ltr text-right" placeholder="۱۲۳۴۵۶۷۸۹۰" />
)}
{/* Pet Selection (if user has pets) */} {pets.length > 0 && (
🐾

اتصال سفارش به پرونده سلامت حیوان خانگی:

{pets.map(p => ( ))}
)} {/* Step 2: Payment Methods */}
۲

شیوه پرداخت

{(() => { const getText = useSettingsStore.getState().getText; const allGateways = [ { id: 'online', label: getText('PAY_GATEWAY_ONLINE_LABEL', 'درگاه پرداخت اینترنتی (زیبال / شتاب)'), desc: 'پرداخت آنلاین و آنی با کلیه کارت‌های شتاب', icon: , enabled: getText('PAY_GATEWAY_ONLINE_ENABLE', 'true') === 'true' }, { id: 'wallet', label: getText('PAY_GATEWAY_WALLET_LABEL', 'اعتبار حساب (کیف پول)'), desc: `موجودی: ${toPersian((profile.walletBalance || 0).toLocaleString())} تومان`, icon: , enabled: getText('PAY_GATEWAY_WALLET_ENABLE', 'true') === 'true' }, { id: 'card', label: getText('PAY_GATEWAY_CARD_LABEL', 'کارت به کارت (واریز مستقیم)'), desc: getText('PAY_GATEWAY_CARD_DESC', 'واریز به شماره کارت بانک سامان'), icon: , enabled: isCardToCardEnabled }, { id: 'cod', label: getText('PAY_GATEWAY_COD_LABEL', 'پرداخت در محل (کارتخوان)'), desc: getText('PAY_GATEWAY_COD_DESC', 'تحویل کالا و پرداخت درب منزل'), icon: , enabled: getText('PAY_GATEWAY_COD_ENABLE', 'false') === 'true' } ]; const sortedGateways = [...allGateways].sort((a, b) => (b.enabled ? 1 : 0) - (a.enabled ? 1 : 0)); return sortedGateways.map((pay) => (
{ if (pay.enabled) { setPaymentMethod(pay.id); } else { toast.info("این روش پرداخت به زودی فعال خواهد شد."); } }} className={cn( "p-3.5 rounded-xl border-2 transition-all flex items-center justify-between gap-3 relative cursor-pointer", pay.enabled ? (paymentMethod === pay.id ? "border-canina-blue bg-canina-blue/5 shadow-xs" : "border-medical-gray-200 hover:border-canina-blue/40") : "border-medical-gray-100 bg-medical-gray-50 opacity-60 cursor-not-allowed" )} >
{pay.icon}

{pay.label}

{pay.desc}

{pay.id === 'wallet' && pay.enabled && ( )}
)); })()}
{isCardToCardEnabled && paymentMethod === 'card' && (
اطلاعات کارت جهت واریز وجه سفارش:
شماره کارت سامان: 6219 8619 7401 2071
صاحب حساب: پارسادرمانی کنینا (نوواگارد)
)}
{/* Charity Donation Banner (Compact) */}

کمک به حیوانات پناهگاه (ردپای مهربانی)

رند کردن مبلغ فاکتور تا {toPersian(roundStep.toLocaleString())} تومان بعدی

{/* Sticky Order Summary Sidebar (4 cols) */}

خلاصه سفارش

{toPersian(items.length)} قلم کالا
{/* Items List (Compact) */}
{items.map((item) => (

{item.product.name}

{toPersian(item.quantity)} بسته

{toPersian((item.product.priceValue * item.quantity).toLocaleString())} ت
))}
{/* Cost Breakdown */}
مجموع اقلام {toPersian(getSubtotal().toLocaleString())} تومان
{isRefillEnabled && isSubscribed && (
رزرو تمدید ({toPersian(refillPercent)}٪ پاداش خرید بعد): {toPersian(Math.round(getSubtotal() * (refillPercent / 100)).toLocaleString())} ت
)} {charityDonation > 0 && (
ردپای مهربانی {toPersian(charityDonation.toLocaleString())} تومان+
)}
هزینه ارسال {shippingFee > 0 ? ( {toPersian(shippingFee.toLocaleString())} تومان ) : ( رایگان )}
مبلغ قابل پرداخت:
{toPersian((getTotal() + shippingFee).toLocaleString())} تومان
{/* Discount Code Input */}
{ e.preventDefault(); const code = (e.currentTarget.elements.namedItem('coupon') as HTMLInputElement).value; const success = await useCartStore.getState().applyCoupon(code); if (success) { toast.success("کد تخفیف با موفقیت اعمال شد."); } else { toast.error("کد تخفیف نامعتبر است."); } }} >
{/* Submit Button */}
تضمین امنیت پرداخت و اصالت کالا
setShowTopUpModal(false)} onConfirm={(amt) => { useUserStore.setState(state => ({ profile: { ...state.profile, walletBalance: (state.profile.walletBalance || 0) + amt } })); }} /> setShowAddressModal(false)} onSave={async (newAddr) => { try { await useUserStore.getState().addAddress(newAddr); const updatedProfile = useUserStore.getState().profile; if (updatedProfile.addresses && updatedProfile.addresses.length > 0) { setSelectedAddressId(updatedProfile.addresses[0].id); } setShowAddressModal(false); } catch (e) { console.error("Failed to add address", e); } }} />
); }