"use client"; import { useState } from "react"; /** * Client form for /shop/topup. Posts the chosen amount to /api/paypal/create, * then redirects the browser to PayPal's approval URL. After PayPal returns to * /shop/topup?token=&status=success, it captures via /api/paypal/capture. */ export default function TopUpForm({ currency, creditsPerUnit, }: { currency: string; creditsPerUnit: number; }) { const [amount, setAmount] = useState("5.00"); const [pending, setPending] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const numeric = Number(amount); const credits = Number.isFinite(numeric) && numeric > 0 ? Math.floor(numeric * creditsPerUnit) : 0; async function startCheckout(e: React.FormEvent) { e.preventDefault(); setError(null); setPending(true); try { const res = await fetch("/api/paypal/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ amount: numeric }), }); const data = (await res.json().catch(() => ({}))) as { approveUrl?: string; error?: string; }; if (!res.ok || !data.approveUrl) { setError(data.error ?? "Could not start the PayPal checkout."); setPending(false); return; } // Hand off to PayPal for approval. const redirectUrl = new URL(data.approveUrl); if (redirectUrl.protocol !== "https:") { throw new Error("Invalid redirect URL: must be HTTPS"); } window.location.href = redirectUrl.href; } catch { setError("Network error — please try again."); setPending(false); } } async function capture(orderId: string) { setPending(true); setError(null); try { const res = await fetch("/api/paypal/capture", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ orderId }), }); const data = (await res.json().catch(() => ({}))) as { ok?: boolean; credits?: number; error?: string; }; if (res.ok && data.ok) { setSuccess( `Payment complete! ${(data.credits ?? 0).toLocaleString()} credits were added.`, ); } else { setError(data.error ?? "We couldn't confirm your payment. Contact staff if charged."); } } catch { setError("Network error confirming payment — contact staff if you were charged."); } finally { setPending(false); // Clean the token/status query so a refresh doesn't re-capture. window.history.replaceState(null, "", "/shop/topup"); } } // On return from PayPal (?token=&status=success), auto-capture once. if (typeof window !== "undefined" && !pending && !success) { const params = new URLSearchParams(window.location.search); const token = params.get("token"); if (token && params.get("status") === "success") { // Defer to avoid setState during render. queueMicrotask(() => capture(token)); } } if (success) { return (

{success}

); } return (
setAmount(e.target.value)} aria-label={`Amount in ${currency}`} required />
{["2.00", "5.00", "10.00", "25.00"].map((v) => ( ))}

You'll receive{" "} {credits.toLocaleString()} credits.

{error ? (

{error}

) : null}
); }