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)}
+ />
);