/** * 微信 H5 支付完成后可能不跳转,通过 cookie 中的订单号轮询检测支付状态 * cookie 格式:order_id|timestamp,仅对 15 分钟内设置的 cookie 轮询,避免陈旧 cookie 误触发 */ import { useEffect, useRef, useState } from "react"; const COOKIE_NAME = "join_pay_order"; const POLL_INTERVAL = 2000; const MAX_POLLS = 150; // 约 5 分钟 const MAX_AGE_MS = 15 * 60 * 1000; // 15 分钟 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`; } /** 解析 cookie:order_id|timestamp,返回 [orderId, isValid]。无时间戳视为过期并清除 */ 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]; // 超过 15 分钟 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; }