From 4a0773e1a5650a95e5c47c3c532ee87cda58c22b Mon Sep 17 00:00:00 2001 From: Roba Boru Date: Sun, 14 Jun 2026 20:40:54 +0300 Subject: [PATCH] Revert payment page --- .../portal/src/app/booking/payment/page.tsx | 387 +++++++++--------- 1 file changed, 191 insertions(+), 196 deletions(-) diff --git a/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx index 716db1168..d271e0042 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/navigation"; import { useBookingStore } from "@/lib/booking-store"; import { usePaymentStore } from "@/lib/payment-store"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation } from "@tanstack/react-query"; import { apiClient } from "@/lib/api-client"; import { useState, useEffect } from "react"; import { @@ -12,24 +12,46 @@ import { Wallet, Loader2, CheckCircle, - ExternalLink, } from "lucide-react"; -const METHOD_ICONS: Record = { - TELEBIRR: Smartphone, - CBE_BIRR: Smartphone, - EBIRR: Smartphone, - CARD: CreditCard, - WALLET: Wallet, -}; - -const METHOD_COLORS: Record = { - TELEBIRR: "bg-orange-50 dark:bg-orange-900/20", - CBE_BIRR: "bg-blue-50 dark:bg-blue-900/20", - EBIRR: "bg-green-50 dark:bg-green-900/20", - CARD: "bg-purple-50 dark:bg-purple-900/20", - WALLET: "bg-indigo-50 dark:bg-indigo-900/20", -}; +// Mock payment methods with Ethiopian providers +const paymentMethods = [ + { + id: "TELEBIRR", + name: "Telebirr", + icon: Smartphone, + description: "Pay with Telebirr mobile money", + color: "bg-orange-50 border-orange-200 hover:border-orange-400", + }, + { + id: "CBE_BIRR", + name: "CBE Birr", + icon: Smartphone, + description: "Pay with CBE Birr", + color: "bg-blue-50 border-blue-200 hover:border-blue-400", + }, + { + id: "EBIRR", + name: "eBirr", + icon: Smartphone, + description: "Pay with eBirr", + color: "bg-green-50 border-green-200 hover:border-green-400", + }, + { + id: "CARD", + name: "Card Payment", + icon: CreditCard, + description: "Pay with credit/debit card", + color: "bg-purple-50 border-purple-200 hover:border-purple-400", + }, + { + id: "WALLET", + name: "Wallet", + icon: Wallet, + description: "Pay from your wallet balance", + color: "bg-indigo-50 border-indigo-200 hover:border-indigo-400", + }, +]; export default function PaymentPage() { const router = useRouter(); @@ -39,100 +61,76 @@ export default function PaymentPage() { const [selectedMethod, setSelectedMethod] = useState(null); const [isProcessing, setIsProcessing] = useState(false); + // Calculate total amount const baseFare = passengers.reduce( (sum) => sum + (selectedSchedule?.baseFareAdult || 0), 0, ); const totalAmount = baseFare; - // Fetch real payment methods - const { data: methodsData, isLoading: methodsLoading } = useQuery({ - queryKey: ["payment-methods"], - queryFn: () => apiClient.get("/payments/methods") as Promise, - }); - - const paymentMethods: any[] = Array.isArray(methodsData) - ? methodsData - : (methodsData as any)?.methods || (methodsData as any)?.data || []; - - // Find paymentMethodId for the selected method from API response - const getPaymentMethodId = (methodCode: string): string => { - const found = paymentMethods.find( - (m: any) => - m.code === methodCode || - m.type === methodCode || - m.name?.toUpperCase().replace(/\s/g, "_") === methodCode || - m.id === methodCode, - ); - return found?.id || found?.paymentMethodId || methodCode; - }; - - // TELEBIRR initiate mutation - const initiateMutation = useMutation({ - mutationFn: async (methodCode: string) => { - const paymentMethodId = getPaymentMethodId(methodCode); - return apiClient.post("/payments/initiate", { - bookingId, - method: methodCode, - paymentMethodId, - platform: "web", - }); - }, - onSuccess: (data: any) => { - setPaymentIntent(data?.paymentIntentId || data?.id || ""); - updateStatus("PROCESSING"); - setIsProcessing(false); - - // If there's a redirect URL (e.g. Telebirr checkout page), open it - if (data?.checkoutUrl || data?.redirectUrl || data?.paymentUrl) { - window.open( - data.checkoutUrl || data.redirectUrl || data.paymentUrl, - "_blank", - ); - } else { - router.push("/booking/confirmation"); - } - }, - onError: (error: any) => { - console.error("Payment initiation failed:", error); - updateStatus("FAILED"); - const msg = - error?.response?.data?.message || - error?.message || - "Payment failed. Please try again."; - alert(msg); - setIsProcessing(false); - }, - }); - - // Generic payment intent mutation (for non-Telebirr methods) const paymentMutation = useMutation({ mutationFn: async (data: any) => { + // Try to call the real API, fallback to mock if it fails try { return await apiClient.post("/payments/intent", data); - } catch { + } catch (error) { + console.log("Payment API not available, using mock payment"); + // Mock payment response return { paymentIntentId: `mock-payment-${Date.now()}`, status: "PENDING", + amountMinor: data.amountMinor, + currency: data.currency, + method: data.method, }; } }, onSuccess: async (data: any) => { setPaymentIntent(data.paymentIntentId); updateStatus("PROCESSING"); + + // Simulate payment processing await new Promise((resolve) => setTimeout(resolve, 2000)); - updateStatus("SUCCEEDED"); - router.push("/booking/confirmation"); + + // Generate tickets after successful payment + try { + await generateTickets(); + updateStatus("SUCCEEDED"); + router.push("/booking/confirmation"); + } catch (error) { + console.error("Ticket generation failed:", error); + // Still proceed to confirmation even if ticket generation fails + updateStatus("SUCCEEDED"); + router.push("/booking/confirmation"); + } }, onError: (error: any) => { + console.error("Payment failed:", error); updateStatus("FAILED"); - alert( - error?.response?.data?.message || "Payment failed. Please try again.", - ); + const errorMessage = + error?.response?.data?.message || + error?.message || + "Payment failed. Please try again."; + alert(errorMessage); setIsProcessing(false); }, }); + const generateTickets = async () => { + // Try to generate tickets via API, fallback to mock + try { + await apiClient.post("/tickets/generate", { + bookingId, + pnr, + }); + } catch (error) { + console.log( + "Ticket API not available, tickets will be generated on confirmation page", + ); + // Mock ticket generation - tickets will be displayed on confirmation page + } + }; + const handlePayment = async () => { if (!selectedMethod || !bookingId) { alert("Please select a payment method"); @@ -141,23 +139,27 @@ export default function PaymentPage() { setIsProcessing(true); - if (selectedMethod === "TELEBIRR") { - debugger; - initiateMutation.mutate(selectedMethod); - } else { - paymentMutation.mutate({ - bookingId, - method: selectedMethod, - currency: selectedCurrency, - amountMinor: totalAmount, - }); - } + paymentMutation.mutate({ + bookingId, + method: selectedMethod, + currency: selectedCurrency, + amountMinor: totalAmount, + }); }; + // Redirect if no booking data (but not during navigation) useEffect(() => { + // Add a small delay to allow state to be set from previous page const timer = setTimeout(() => { - if (!bookingId || !pnr) router.push("/booking/search"); + if (!bookingId || !pnr) { + console.log( + "Payment page: Missing booking data, redirecting to search", + ); + console.log("bookingId:", bookingId, "pnr:", pnr); + router.push("/booking/search"); + } }, 500); + return () => clearTimeout(timer); }, [bookingId, pnr, router]); @@ -172,9 +174,6 @@ export default function PaymentPage() { ); } - const isBusy = - isProcessing || initiateMutation.isPending || paymentMutation.isPending; - return (
@@ -187,22 +186,37 @@ export default function PaymentPage() { {pnr}

- {/* Processing overlay */} - {isBusy && ( -
-
- -

- Processing payment -

-

- Please wait… -

+ {/* Payment Processing Overlay */} + {isProcessing && ( +
+
+ {paymentMutation.isSuccess ? ( + <> + +

+ Payment successful! +

+

+ Generating your tickets... +

+ + + ) : ( + <> + +

+ Processing payment +

+

+ Please wait while we process your payment... +

+ + )}
)} - {/* Order summary */} + {/* Order Summary */}

Order summary @@ -244,7 +258,7 @@ export default function PaymentPage() { Total amount - + ETB {(totalAmount / 100).toFixed(2)}

@@ -252,120 +266,101 @@ export default function PaymentPage() {
- {/* Payment methods */} + {/* Payment Methods */}

Select payment method

- - {methodsLoading ? ( -
- - Loading payment methods… -
- ) : paymentMethods.length === 0 ? ( -

- No payment methods available. -

- ) : ( -
- {paymentMethods.map((method: any) => { - const code: string = - method.code || method.type || method.id || ""; - const name: string = - method.name || method.displayName || code; - const description: string = - method.description || `Pay with ${name}`; - const isSelected = selectedMethod === code; - const Icon = METHOD_ICONS[code] || Smartphone; - const bgColor = - METHOD_COLORS[code] || "bg-gray-50 dark:bg-gray-800/50"; - - return ( - - ); - })} -
- )} +
+

+ {method.name} +

+

+ {method.description} +

+
+ {isSelected && ( +
+ +
+ )} +
+ + ); + })} +
- {/* Actions */} + {/* Action Buttons */}
+
- {(initiateMutation.isError || paymentMutation.isError) && ( -
-

- ⚠️ Payment failed. Please try again or contact support. + {/* Error Message */} + {paymentMutation.isError && ( +

+

+ ⚠️ Payment failed. Please try again or contact support if the + problem persists.

)} -
-

+ {/* Security Notice */} +

+

🔒 Your payment is secure and encrypted. We do not store your payment information.