diff --git a/driver-pwa/frontend/src/hooks/usePaymentStatus.ts b/driver-pwa/frontend/src/hooks/usePaymentStatus.ts new file mode 100644 index 0000000..bee1229 --- /dev/null +++ b/driver-pwa/frontend/src/hooks/usePaymentStatus.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query"; +import { getPaymentState } from "@/api/driver"; + +export function usePaymentStatus(orderId: string) { + return useQuery({ + queryKey: ["payment", orderId], + queryFn: () => getPaymentState(orderId), + refetchInterval: (q) => (q.state.data?.status === "PAID" || q.state.data?.status === "REJECTED" ? false : 1500), + }); +} diff --git a/driver-pwa/frontend/src/pages/PaymentPage.test.tsx b/driver-pwa/frontend/src/pages/PaymentPage.test.tsx new file mode 100644 index 0000000..1490e6e --- /dev/null +++ b/driver-pwa/frontend/src/pages/PaymentPage.test.tsx @@ -0,0 +1,29 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { MemoryRouter, Routes, Route } from "react-router-dom"; + +const states = ["FORM", "PAID"]; +vi.mock("@/api/driver", () => ({ + getPaymentState: vi.fn(async () => ({ order_id: "o9", status: states.shift() ?? "PAID" })), +})); +import { PaymentPage } from "./PaymentPage"; + +function wrap() { + const qc = new QueryClient(); + return render( + + + } /> + + + ); +} + +describe("PaymentPage", () => { + it("shows pay button and flips to success on PAID", async () => { + wrap(); + expect(screen.getByRole("link", { name: /Оплатить/i })).toHaveAttribute("href", "https://qr.nspk.ru/x"); + await waitFor(() => expect(screen.getByText(/Оплачено/i)).toBeInTheDocument(), { timeout: 3000 }); + }); +}); diff --git a/driver-pwa/frontend/src/pages/PaymentPage.tsx b/driver-pwa/frontend/src/pages/PaymentPage.tsx index c093dad..542c322 100644 --- a/driver-pwa/frontend/src/pages/PaymentPage.tsx +++ b/driver-pwa/frontend/src/pages/PaymentPage.tsx @@ -1 +1,42 @@ -export function PaymentPage() { return null; } +import { useEffect } from "react"; +import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { useQueryClient } from "@tanstack/react-query"; +import { Check } from "lucide-react"; +import { formatMoney } from "@/lib/format"; +import { Spinner } from "@/components/Spinner"; +import { usePaymentStatus } from "@/hooks/usePaymentStatus"; + +export function PaymentPage() { + const { orderId = "" } = useParams(); + const nav = useNavigate(); + const qc = useQueryClient(); + const st = (useLocation().state as { payUrl?: string; total?: number; bucket?: string; base?: number } | null) ?? {}; + const { data } = usePaymentStatus(orderId); + const paid = data?.status === "PAID"; + + useEffect(() => { + if (paid) qc.invalidateQueries({ queryKey: ["balance"] }); + }, [paid, qc]); + + if (paid) { + return ( +
+ +

Оплачено

+

{st.bucket} пополнен на {formatMoney(st.base ?? 0)}

+ +
+ ); + } + + return ( +
+ СБП +

{formatMoney(st.total ?? 0)}

+

{st.bucket}

+ Оплатить +

Откроется ваше приложение банка

+
Ожидаем оплату…
+
+ ); +}