/** * Payment Form Page * Add/Edit Payment form */ import React, { useState, useEffect, useMemo } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, Save, Loader2, Info } from "lucide-react"; import { __ } from "../lib/i18n"; import { apiService } from "../lib/api-client"; import { todayYmd } from "../lib/dateFormat"; import { usePermissions } from "../hooks/usePermissions"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Select } from "../components/ui/select"; import { DatePicker } from "../components/ui/date-picker"; import { PageHeader } from "../components/common/PageHeader"; import { BookingPicker } from "../components/common/BookingPicker"; import { Card, CardContent, CardHeader, CardTitle, } from "../components/ui/card"; import { ConditionalRender } from "../components/ui/conditional-render"; import { HelpText } from "../components/ui/help-text"; import { Alert } from "../components/ui/alert"; interface PaymentFormData { booking_id: string; amount: string; payment_method: string; payment_status: "pending" | "completed" | "failed" | "refunded" | "partial"; payment_date: string; transaction_id: string; notes: string; } const PaymentForm: React.FC = () => { const queryClient = useQueryClient(); const { can } = usePermissions(); const [formData, setFormData] = useState({ booking_id: "", amount: "", payment_method: "Credit Card", payment_status: "pending", payment_date: todayYmd(), transaction_id: "", notes: "", }); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); // Get action and id from URL const action = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("action") || "create"; }, []); const paymentId = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("id") ? parseInt(params.get("id") || "0") : null; }, []); const isEditMode = action === "edit" && paymentId !== null; // Fetch payment data if editing const { data: paymentData, isLoading: isLoadingPayment } = useQuery({ queryKey: ["payment", paymentId], queryFn: async () => { if (!paymentId) return null; const result = await apiService.getPayment(paymentId); if (!result) { throw new Error("Failed to fetch payment"); } if (!result.success) { throw new Error(result.message || "Payment not found"); } const data = result.data; const processedAt = data.processed_at; const processedDate = typeof processedAt === "string" && processedAt.includes(" ") ? processedAt.split(" ")[0] : todayYmd(); return { id: data.id, booking_id: data.booking_id, amount: data.amount, payment_method: data.gateway, payment_status: data.status, payment_date: processedDate, transaction_id: data.transaction_id ?? "", notes: data.notes ?? "", }; }, enabled: isEditMode && can("yatra_view_bookings"), }); // Load payment data into form when editing useEffect(() => { if (paymentData && isEditMode) { setFormData({ booking_id: String(paymentData.booking_id ?? ""), amount: String(paymentData.amount ?? ""), payment_method: String(paymentData.payment_method || "Credit Card"), payment_status: (String(paymentData.payment_status || "pending") || "pending") as PaymentFormData["payment_status"], payment_date: String(paymentData.payment_date || todayYmd()), transaction_id: String(paymentData.transaction_id ?? ""), notes: String(paymentData.notes ?? ""), }); } }, [paymentData, isEditMode]); const handleFieldChange = (field: keyof PaymentFormData, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { setErrors((prev) => ({ ...prev, [field]: "" })); } }; const validateForm = (): boolean => { const newErrors: Record = {}; if (!formData.booking_id.trim()) { newErrors.booking_id = __("Booking is required", "yatra"); } else { const bookingId = parseInt(formData.booking_id); if (isNaN(bookingId) || bookingId <= 0) { newErrors.booking_id = __("Valid booking ID is required", "yatra"); } } if (!formData.amount.trim()) { newErrors.amount = __("Payment amount is required", "yatra"); } else { const amount = parseFloat(formData.amount); if (isNaN(amount) || amount <= 0) { newErrors.amount = __( "Payment amount must be a positive number", "yatra", ); } } if (!formData.payment_method.trim()) { newErrors.payment_method = __("Payment method is required", "yatra"); } if (!formData.payment_date.trim()) { newErrors.payment_date = __("Payment date is required", "yatra"); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Create/Update mutation const saveMutation = useMutation({ mutationFn: async (data: PaymentFormData) => { const payload = { booking_id: parseInt(data.booking_id), amount: parseFloat(data.amount), gateway: data.payment_method, status: data.payment_status, processed_at: data.payment_date, transaction_id: data.transaction_id.trim() || null, notes: data.notes.trim() || null, }; return isEditMode && paymentId ? await apiService.updatePayment(paymentId, payload) : await apiService.createPayment(payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["payments"] }); queryClient.invalidateQueries({ queryKey: ["bookings"] }); // Redirect to payments list window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=payments`; }, onError: (error: any) => { console.error("Error saving payment:", error); setErrors({ submit: error.message || __("Failed to save payment", "yatra"), }); }, }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { return; } setIsSubmitting(true); try { await saveMutation.mutateAsync(formData); } catch (error) { // Error handled in mutation } finally { setIsSubmitting(false); } }; const handleBack = () => { window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=payments`; }; if (isLoadingPayment) { return (
); } return (
{__("Back", "yatra")} } />
{/* Main Form Fields */}
{/* Payment Information */} {__("Payment Information", "yatra")} {/* Booking */}
handleFieldChange("booking_id", id)} error={Boolean(errors.booking_id)} disabled={isEditMode} placeholder={__("Select a booking…", "yatra")} searchPlaceholder={__( "Search by booking code, name, or email…", "yatra", )} /> {errors.booking_id && (

{errors.booking_id}

)} {isEditMode && (

{__( "The booking linked to a payment cannot be changed after creation.", "yatra", )}

)}
{/* Amount and Payment Method */}
{/* Amount */}
$ handleFieldChange("amount", e.target.value) } placeholder={__("e.g., 2500.00", "yatra")} className={`pl-7 ${errors.amount ? "border-red-500" : ""}`} required />
{errors.amount && (

{errors.amount}

)}
{/* Payment Method */}
{errors.payment_method && (

{errors.payment_method}

)}
{/* Payment Date and Transaction ID */}
{/* Payment Date */}
handleFieldChange("payment_date", value) } placeholder={__("Select payment date", "yatra")} maxDate={new Date()} error={Boolean(errors.payment_date)} /> {errors.payment_date && (

{errors.payment_date}

)}
{/* Transaction ID */}
handleFieldChange("transaction_id", e.target.value) } placeholder={__("e.g., TXN123456789", "yatra")} className={ errors.transaction_id ? "border-red-500" : "" } /> {errors.transaction_id && (

{errors.transaction_id}

)}
{/* Notes */}