diff --git a/apps/edr-freight-web/portal/src/pages/billing/InvoiceDetailPage.tsx b/apps/edr-freight-web/portal/src/pages/billing/InvoiceDetailPage.tsx index ce47fa691..6fbb15d62 100644 --- a/apps/edr-freight-web/portal/src/pages/billing/InvoiceDetailPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/billing/InvoiceDetailPage.tsx @@ -15,12 +15,15 @@ import { Text, Title, } from "@mantine/core"; -import { ArrowLeft, CreditCard, Download, ExternalLink, Info, Receipt } from "lucide-react"; +import { ArrowLeft, CreditCard, Download, ExternalLink, Receipt } from "lucide-react"; +import { useState } from "react"; import toast from "react-hot-toast"; import { api } from "@/services/api"; import { invoicesService } from "@/services/invoices.service"; +import { paymentsService, type PaymentMethod } from "@/services/payments.service"; import { warehouseInvoicesService } from "@/services/warehouse-invoices.service"; +import { PaymentMethodModal } from "@/pages/bookings/BookingDetailPage/components/PaymentMethodModal"; import { saveBlob } from "@/utils/download"; import { formatCurrency } from "@/lib/currency"; import { BORDER, INK, MUTED } from "../contracts/contract-ui"; @@ -53,14 +56,29 @@ export default function InvoiceDetailPage() { api.invoices.get.queryOptions({ input: { id } }), ); - const payMutation = useMutation( - api.invoices.pay.mutationOptions({ - onSuccess: (res) => { - const url = res.clientAction?.url; - if (url) window.location.href = url; - }, - }), - ); + const [payModalOpen, setPayModalOpen] = useState(false); + + // Extracted for payMutation callbacks — guaranteed defined when they run + // (guarded by the early return below). + const invSource = invoice?.source; + const invSourceId = invoice?.sourceId; + + const payMutation = useMutation({ + mutationFn: async (method: PaymentMethod) => { + const bookingId = + invSource === "warehouse" + ? (await warehouseInvoicesService.get(id)).bookingId ?? invSourceId! + : invSourceId!; + return api.payments.initiate.call({ bookingId, method }); + }, + onSuccess: (data, method) => { + const redirectUrl = + data?.clientAction?.type === "REDIRECT" && data.clientAction.url + ? data.clientAction.url + : paymentsService.checkoutUrl({ bookingId: invSourceId!, method }); + window.location.href = redirectUrl; + }, + }); if (isLoading) { return ( @@ -94,9 +112,7 @@ export default function InvoiceDetailPage() { const lines = invoice.lines ?? []; const handlePay = () => { - const returnUrl = `${window.location.origin}/payment/success`; - const failureUrl = `${window.location.origin}/payment/failure`; - payMutation.mutate({ id, payload: { returnUrl, failureUrl } }); + setPayModalOpen(true); }; const hasReceipt = Number(invoice.paidAmount) > 0; @@ -221,12 +237,6 @@ export default function InvoiceDetailPage() { - {payMutation.isError && ( - } title="Payment could not be started"> - Please try again, or contact support if the problem persists. - - )} - {/* Summary */} @@ -330,6 +340,27 @@ export default function InvoiceDetailPage() { + + { + if (!payMutation.isPending) { + setPayModalOpen(false); + payMutation.reset(); + } + }} + amountLabel={formatCurrency(Number(invoice.totalAmount), invoice.currency)} + currency={invoice.currency} + processing={payMutation.isPending} + error={ + payMutation.isError + ? payMutation.error instanceof Error + ? payMutation.error.message + : "Could not start payment. Please try again." + : null + } + onConfirm={(method) => payMutation.mutate(method)} + /> );