feat(payment): share payment method selector
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
import type { PaymentMethodConfig } from "@/lib/payment/payment_method";
|
||||
|
||||
import { usePaymentMethodSelection } from "../use-payment-method-selection";
|
||||
|
||||
describe("usePaymentMethodSelection", () => {
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
beforeEach(() => {
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
|
||||
.IS_REACT_ACT_ENVIRONMENT = true;
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it("forces Stripe when payment method selection is unavailable", () => {
|
||||
const onChange = vi.fn();
|
||||
renderHarness(root, {
|
||||
config: { canChoosePaymentMethod: false, initialPayChannel: "stripe" },
|
||||
currentPayChannel: "ezpay",
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith("stripe");
|
||||
});
|
||||
|
||||
it("applies the default channel once when no return channel was requested", () => {
|
||||
const onChange = vi.fn();
|
||||
const config = {
|
||||
canChoosePaymentMethod: true,
|
||||
initialPayChannel: "ezpay",
|
||||
} as const;
|
||||
renderHarness(root, {
|
||||
config,
|
||||
currentPayChannel: "stripe",
|
||||
onChange,
|
||||
});
|
||||
renderHarness(root, {
|
||||
config,
|
||||
currentPayChannel: "ezpay",
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith("ezpay");
|
||||
});
|
||||
|
||||
it("preserves an explicit return channel and pauses while payment is busy", () => {
|
||||
const onChange = vi.fn();
|
||||
renderHarness(root, {
|
||||
config: { canChoosePaymentMethod: true, initialPayChannel: "ezpay" },
|
||||
currentPayChannel: "stripe",
|
||||
requestedPayChannel: "stripe",
|
||||
onChange,
|
||||
});
|
||||
renderHarness(root, {
|
||||
config: { canChoosePaymentMethod: false, initialPayChannel: "stripe" },
|
||||
currentPayChannel: "ezpay",
|
||||
isPaymentBusy: true,
|
||||
onChange,
|
||||
});
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
interface HarnessProps {
|
||||
config: PaymentMethodConfig;
|
||||
currentPayChannel: PayChannel;
|
||||
requestedPayChannel?: PayChannel | null;
|
||||
isPaymentBusy?: boolean;
|
||||
onChange: (payChannel: PayChannel) => void;
|
||||
}
|
||||
|
||||
function renderHarness(root: Root, props: HarnessProps): void {
|
||||
act(() => {
|
||||
root.render(<Harness {...props} />);
|
||||
});
|
||||
}
|
||||
|
||||
function Harness({
|
||||
config,
|
||||
currentPayChannel,
|
||||
requestedPayChannel = null,
|
||||
isPaymentBusy = false,
|
||||
onChange,
|
||||
}: HarnessProps) {
|
||||
usePaymentMethodSelection({
|
||||
config,
|
||||
currentPayChannel,
|
||||
requestedPayChannel,
|
||||
isPaymentBusy,
|
||||
onChange,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import type { PayChannel } from "@/data/schemas/payment";
|
||||
import type { PaymentMethodConfig } from "@/lib/payment/payment_method";
|
||||
|
||||
export interface UsePaymentMethodSelectionInput {
|
||||
config: PaymentMethodConfig;
|
||||
currentPayChannel: PayChannel;
|
||||
isPaymentBusy: boolean;
|
||||
requestedPayChannel: PayChannel | null;
|
||||
onChange: (payChannel: PayChannel) => void;
|
||||
}
|
||||
|
||||
export function usePaymentMethodSelection({
|
||||
config,
|
||||
currentPayChannel,
|
||||
isPaymentBusy,
|
||||
requestedPayChannel,
|
||||
onChange,
|
||||
}: UsePaymentMethodSelectionInput): void {
|
||||
const appliedDefaultChannelRef = useRef<PayChannel | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPaymentBusy) return;
|
||||
|
||||
if (!config.canChoosePaymentMethod) {
|
||||
appliedDefaultChannelRef.current = null;
|
||||
if (currentPayChannel !== "stripe") onChange("stripe");
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestedPayChannel !== null) return;
|
||||
if (appliedDefaultChannelRef.current === config.initialPayChannel) return;
|
||||
appliedDefaultChannelRef.current = config.initialPayChannel;
|
||||
|
||||
if (currentPayChannel !== config.initialPayChannel) {
|
||||
onChange(config.initialPayChannel);
|
||||
}
|
||||
}, [
|
||||
config.canChoosePaymentMethod,
|
||||
config.initialPayChannel,
|
||||
currentPayChannel,
|
||||
isPaymentBusy,
|
||||
onChange,
|
||||
requestedPayChannel,
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user