/** * 微信 H5 支付完成后可能不跳转,通过 cookie 轮询检测支付状态 * 参考 digital usePayStatusPoll */ import { useEffect, useRef, useState } from "react"; const COOKIE_NAME = "nomadvip_pay_order"; const POLL_INTERVAL = 2000; const MAX_POLLS = 150; const MAX_AGE_MS = 15 * 60 * 1000; function getCookie(name: string): string | null { if (typeof document === "undefined") return null; const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)); return match ? decodeURIComponent(match[1]) : null; } function clearCookie(name: string): void { if (typeof document === "undefined") return; document.cookie = `${name}=; path=/; max-age=0`; } function parsePayOrderCookie(raw: string | null): [string | null, boolean] { if (!raw?.trim()) return [null, false]; const parts = raw.split("|"); const orderId = parts[0]?.trim() || raw.trim(); const ts = parts[1] ? parseInt(parts[1], 10) : NaN; if (!orderId) return [null, false]; if (Number.isNaN(ts)) return [orderId, false]; if (Date.now() - ts > MAX_AGE_MS) return [orderId, false]; return [orderId, true]; } export function usePayStatusPoll(onPaid: () => void): boolean { const [polling, setPolling] = useState(false); const onPaidRef = useRef(onPaid); onPaidRef.current = onPaid; useEffect(() => { const raw = getCookie(COOKIE_NAME); const [orderId, isValid] = parsePayOrderCookie(raw); if (!orderId || !isValid) { if (raw) clearCookie(COOKIE_NAME); return; } setPolling(true); let count = 0; const timer = setInterval(async () => { count++; if (count > MAX_POLLS) { clearInterval(timer); clearCookie(COOKIE_NAME); setPolling(false); return; } try { const res = await fetch(`/api/pay/status?order_id=${encodeURIComponent(orderId)}`); const data = await res.json(); if (data?.ok && data?.paid) { clearInterval(timer); clearCookie(COOKIE_NAME); setPolling(false); onPaidRef.current(); } } catch { // 继续轮询 } }, POLL_INTERVAL); return () => clearInterval(timer); }, []); return polling; }