"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, Edit2 } 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, { AddressFormData, validateAddressForm, normalizeDigits } from "./AddressModal"; import SearchableSelect from "./SearchableSelect"; import { IRAN_PROVINCES, PROVINCE_CITIES } from "../lib/data/provinces"; import { useCatalogMode } from "../lib/useCatalogMode"; import { toast } from "sonner"; import { useRouter } from 'next/navigation'; import api from "../lib/services/api"; export default function CheckoutPage() { const router = useRouter(); const { showPrices } = useCatalogMode(); const { items, getTotal, getSubtotal, isSubscribed, charityDonation, setCharityDonation, addOrder, clearCart } = useCartStore(); const { pets, activePetId, getActivePet, updatePet } = usePetStore(); const { profile, isLoggedIn } = useUserStore(); const [isHydrated, setIsHydrated] = useState(false); const [loading, setLoading] = useState(false); useEffect(() => { setIsHydrated(true); }, []); const [useRoundUp, setUseRoundUp] = useState(false); const [paymentMethod, setPaymentMethod] = useState('card'); const [showTopUpModal, setShowTopUpModal] = useState(false); const [showAddressModal, setShowAddressModal] = useState(false); const [editingAddress, setEditingAddress] = useState(null); 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 matching AddressModal if guest or not using stored addresses const [manualAddressData, setManualAddressData] = useState({ title: 'منزل اصلی', receptorName: '', phone: '', province: '', city: '', detail: '', zipCode: '', isDefault: true }); const [manualErrors, setManualErrors] = 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 || profile.mobile)) { // Pre-fill profile info for manual fields setManualAddressData(prev => ({ ...prev, receptorName: prev.receptorName || `${profile.firstName || ''} ${profile.lastName || ''}`.trim(), phone: prev.phone || profile.mobile || '' })); } }); }, [isLoggedIn, profile]); const isCharityEnabled = useSettingsStore((state) => state.getBoolean('CHARITY_DONATION_ENABLED', true) && state.getBoolean('charity_donation_enabled', true) && state.getBoolean('charityDonationEnabled', true) ); 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 effectiveCharityDonation = isCharityEnabled ? charityDonation : 0; const handleCharityToggle = () => { if (!isCharityEnabled) return; if (!useRoundUp) { setCharityDonation(roundUpDiff); setUseRoundUp(true); } else { setCharityDonation(0); setUseRoundUp(false); } }; const handleFinalize = async () => { const totalAmount = getTotal() + shippingFee; const activePet = getActivePet(); // 0. Ensure user is logged in before placing order if (!isLoggedIn) { toast.error("جهت تکمیل خرید و ثبت سفارش، لطفاً ابتدا وارد حساب کاربری خود شوید."); useUserStore.getState().setAuthModalOpen(true); return; } // 1. Validate and resolve shipping address const selectedAddr = profile.addresses?.find(a => a.id === selectedAddressId); let shippingAddressStr = ''; if (selectedAddr) { const cleanPhone = normalizeDigits(selectedAddr.phone); const cleanZip = normalizeDigits(selectedAddr.zipCode); const text = `${selectedAddr.province}، ${selectedAddr.city}، ${selectedAddr.detail}${cleanZip ? ` (کد پستی: ${cleanZip})` : ''} (گیرنده: ${selectedAddr.receptorName} - ${toPersian(cleanPhone)})`; shippingAddressStr = JSON.stringify({ province: selectedAddr.province, city: selectedAddr.city, address: selectedAddr.detail, postalCode: cleanZip, fullName: selectedAddr.receptorName, phone: cleanPhone, formatted: text }); } else { const addrErrors = validateAddressForm(manualAddressData); setManualErrors(addrErrors); if (Object.keys(addrErrors).length > 0) { toast.error("لطفاً اطلاعات آدرس و گیرنده را به صورت کامل و صحیح تکمیل کنید."); return; } const cleanPhone = normalizeDigits(manualAddressData.phone); const cleanZip = normalizeDigits(manualAddressData.zipCode); const text = `${manualAddressData.province}، ${manualAddressData.city}، ${manualAddressData.detail}${cleanZip ? ` (کد پستی: ${cleanZip})` : ''} (گیرنده: ${manualAddressData.receptorName} - ${toPersian(cleanPhone)})`; shippingAddressStr = JSON.stringify({ province: manualAddressData.province, city: manualAddressData.city, address: manualAddressData.detail, postalCode: cleanZip, fullName: manualAddressData.receptorName, phone: cleanPhone, formatted: text }); // Auto-save the new manual address to user's permanent address list try { await useUserStore.getState().addAddress({ id: Math.random().toString(36).substring(2, 9), title: manualAddressData.title || 'منزل اصلی', receptorName: manualAddressData.receptorName, phone: cleanPhone, province: manualAddressData.province, city: manualAddressData.city, detail: manualAddressData.detail, zipCode: cleanZip, isDefault: manualAddressData.isDefault }); } catch (e) { console.error('Failed to save manual address to user account:', e); } // Auto-sync user's first and last name if not set or default const currentFirst = profile.firstName?.trim() || ''; const currentLast = profile.lastName?.trim() || ''; if (!currentFirst || currentFirst === 'کاربر' || !currentLast || currentLast === 'جدید') { const nameParts = manualAddressData.receptorName.trim().split(/\s+/); const newFirst = nameParts[0] || 'کاربر'; const newLast = nameParts.slice(1).join(' ') || 'گرامی'; try { await useUserStore.getState().updateProfile({ firstName: newFirst, lastName: newLast }); } catch (e) { console.error('Failed to update profile names:', e); } } } // 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: effectiveCharityDonation, 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 { isCatalogOnly, allowCheckout, orderDisabledTitle, orderDisabledMessage } = useCatalogMode(); const isCheckoutDisabled = isCatalogOnly && !allowCheckout; if (isCheckoutDisabled) { return (

{orderDisabledTitle}

{orderDisabledMessage}

تماس با پشتیبانی ({getText('contact_phone', '۰۲۱-۸۸۸۸۴۴۴۴')})
); } if (isHydrated && 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 group relative", 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)})

))}
) : (
{/* Row 1: Title */}
setManualAddressData({...manualAddressData, title: e.target.value})} className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20 text-xs", manualErrors.title ? "border-red-300" : "border-medical-gray-100")} placeholder="مثال: منزل اصلی" /> {manualErrors.title &&

{manualErrors.title}

}
{/* Row 2: Name & Phone */}
setManualAddressData({...manualAddressData, receptorName: e.target.value})} className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20 text-xs", manualErrors.receptorName ? "border-red-300" : "border-medical-gray-100")} placeholder="نام و نام خانوادگی..." /> {manualErrors.receptorName &&

{manualErrors.receptorName}

}
setManualAddressData({...manualAddressData, phone: e.target.value})} className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20 text-left text-xs", manualErrors.phone ? "border-red-300" : "border-medical-gray-100")} placeholder="09120000000" dir="ltr" /> {manualErrors.phone &&

{manualErrors.phone}

}
{/* Row 3: Province & City */}
{ const cities = PROVINCE_CITIES[newProv] || []; setManualAddressData({ ...manualAddressData, province: newProv, city: cities[0] || "" }); }} /> {manualErrors.province &&

{manualErrors.province}

}
{manualAddressData.province && PROVINCE_CITIES[manualAddressData.province] ? ( setManualAddressData({...manualAddressData, city: c})} /> ) : ( setManualAddressData({...manualAddressData, city: e.target.value})} className={cn("w-full bg-medical-gray-50 border rounded-2xl py-3 px-5 outline-none font-bold transition-all focus:ring-2 focus:ring-canina-blue/20 text-xs", manualErrors.city ? "border-red-300" : "border-medical-gray-100")} placeholder="نام شهر..." /> )} {manualErrors.city &&

{manualErrors.city}

}
{/* Row 4: Detail Address */}