feat(driver-pwa): top-up fetches commission rate from backend (no hardcoded rate)
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -15,6 +15,7 @@ export const TopupSchema = z.object({
|
|||||||
order_id: z.string(), pay_url: z.string(), commission: z.number(), total: z.number(),
|
order_id: z.string(), pay_url: z.string(), commission: z.number(), total: z.number(),
|
||||||
});
|
});
|
||||||
export const PaymentStateSchema = z.object({ order_id: z.string(), status: z.string() });
|
export const PaymentStateSchema = z.object({ order_id: z.string(), status: z.string() });
|
||||||
|
export const CommissionSchema = z.object({ rate: z.number() });
|
||||||
export const PaymentsSchema = z.object({
|
export const PaymentsSchema = z.object({
|
||||||
payments: z.array(z.object({
|
payments: z.array(z.object({
|
||||||
order_id: z.string(), bucket: z.string(), amount: z.number(), commission: z.number(),
|
order_id: z.string(), bucket: z.string(), amount: z.number(), commission: z.number(),
|
||||||
@@ -35,3 +36,5 @@ export const getPaymentState = async (orderId: string) =>
|
|||||||
PaymentStateSchema.parse(await api.get(`driver/payment/${orderId}`).json());
|
PaymentStateSchema.parse(await api.get(`driver/payment/${orderId}`).json());
|
||||||
export const getPayments = async () =>
|
export const getPayments = async () =>
|
||||||
PaymentsSchema.parse(await api.get("driver/payments").json());
|
PaymentsSchema.parse(await api.get("driver/payments").json());
|
||||||
|
export const getCommission = async () =>
|
||||||
|
CommissionSchema.parse(await api.get("driver/commission").json());
|
||||||
|
|||||||
@@ -2,14 +2,23 @@ import { describe, it, expect, vi } from "vitest";
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
|
||||||
vi.mock("@/api/driver", () => ({
|
vi.mock("@/api/driver", () => ({
|
||||||
topup: vi.fn(async () => ({ order_id: "o9", pay_url: "https://qr.nspk.ru/x", commission: 52, total: 1052 })),
|
topup: vi.fn(async () => ({ order_id: "o9", pay_url: "https://qr.nspk.ru/x", commission: 52, total: 1052 })),
|
||||||
|
getCommission: vi.fn(async () => ({ rate: 0.052 })),
|
||||||
}));
|
}));
|
||||||
import { TopupPage } from "./TopupPage";
|
import { TopupPage } from "./TopupPage";
|
||||||
import * as driver from "@/api/driver";
|
import * as driver from "@/api/driver";
|
||||||
|
|
||||||
const wrap = () => render(<MemoryRouter><TopupPage /></MemoryRouter>);
|
const wrap = () => {
|
||||||
|
const qc = new QueryClient();
|
||||||
|
return render(
|
||||||
|
<QueryClientProvider client={qc}>
|
||||||
|
<MemoryRouter><TopupPage /></MemoryRouter>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
describe("TopupPage", () => {
|
describe("TopupPage", () => {
|
||||||
it("keypad builds amount, shows total, and submits topup", async () => {
|
it("keypad builds amount, shows total, and submits topup", async () => {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Delete } from "lucide-react";
|
import { Delete } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { topup } from "@/api/driver";
|
import { topup, getCommission } from "@/api/driver";
|
||||||
import { keypadReduce, withCommission } from "@/lib/amount";
|
import { keypadReduce, withCommission } from "@/lib/amount";
|
||||||
import { formatMoney } from "@/lib/format";
|
import { formatMoney } from "@/lib/format";
|
||||||
import { Segment } from "@/components/Segment";
|
import { Segment } from "@/components/Segment";
|
||||||
import { Spinner } from "@/components/Spinner";
|
import { Spinner } from "@/components/Spinner";
|
||||||
|
|
||||||
const RATE = 0.052;
|
|
||||||
const QUICK = ["500", "1000", "2000"];
|
const QUICK = ["500", "1000", "2000"];
|
||||||
const KEYS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "·", "0", "back"];
|
const KEYS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "·", "0", "back"];
|
||||||
|
|
||||||
@@ -19,9 +19,11 @@ export function TopupPage() {
|
|||||||
const [raw, setRaw] = useState("");
|
const [raw, setRaw] = useState("");
|
||||||
const [method, setMethod] = useState("qr");
|
const [method, setMethod] = useState("qr");
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
const { data: commission } = useQuery({ queryKey: ["commission"], queryFn: getCommission });
|
||||||
|
const rate = commission?.rate ?? 0.052; // fallback only while loading
|
||||||
|
|
||||||
const base = raw ? parseInt(raw, 10) : 0;
|
const base = raw ? parseInt(raw, 10) : 0;
|
||||||
const { commission, total } = withCommission(base, RATE);
|
const { commission: fee, total } = withCommission(base, rate);
|
||||||
|
|
||||||
function press(k: string) {
|
function press(k: string) {
|
||||||
if (k === "·") return;
|
if (k === "·") return;
|
||||||
@@ -46,7 +48,7 @@ export function TopupPage() {
|
|||||||
<button className="text-muted text-xs mb-3" onClick={() => nav(-1)}>‹ {bucket}</button>
|
<button className="text-muted text-xs mb-3" onClick={() => nav(-1)}>‹ {bucket}</button>
|
||||||
<p data-testid="amount" className="text-3xl font-extrabold text-center mt-2">{formatMoney(base)}</p>
|
<p data-testid="amount" className="text-3xl font-extrabold text-center mt-2">{formatMoney(base)}</p>
|
||||||
<p data-testid="total" className="text-muted text-xs text-center mb-3">
|
<p data-testid="total" className="text-muted text-xs text-center mb-3">
|
||||||
к оплате {formatMoney(total)} (комиссия {formatMoney(commission)})
|
к оплате {formatMoney(total)} (комиссия {formatMoney(fee)})
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex gap-1.5 mb-3">
|
<div className="flex gap-1.5 mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user