Files
gitlab-instance-0a899031_no…/app/lib/usePayStatusPoll.ts
2026-03-10 09:03:17 -05:00

91 lines
2.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 支付状态轮询
* - 手机 H5传入 orderId点击支付后由 state 传入),直接轮询,不依赖 sessionStorage/cookie
* - PC/微信:不传 orderId使用 cookie 轮询(兼容旧逻辑)
*/
import { useEffect, useRef, useState } from "react";
const COOKIE_NAME = "nomadvip_pay_order";
const POLL_INTERVAL = 1000;
const MAX_POLLS = 160; // ~5 分钟
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: (orderId?: string) => void,
onTimeout?: () => void,
/** 手机 H5点击支付后传入的 orderId直接轮询不依赖 sessionStorage/cookie */
orderIdFromState?: string | null
): boolean {
const [polling, setPolling] = useState(false);
const onPaidRef = useRef(onPaid);
const onTimeoutRef = useRef(onTimeout);
onPaidRef.current = onPaid;
onTimeoutRef.current = onTimeout;
useEffect(() => {
const orderId =
orderIdFromState !== undefined
? (orderIdFromState?.trim() || null)
: (() => {
const raw = getCookie(COOKIE_NAME);
const [oid, valid] = parsePayOrderCookie(raw);
return valid ? oid : null;
})();
if (!orderId) {
if (orderIdFromState === undefined && getCookie(COOKIE_NAME)) 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);
onTimeoutRef.current?.();
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(orderId);
}
} catch {
// 继续轮询
}
}, POLL_INTERVAL);
return () => clearInterval(timer);
}, [orderIdFromState]);
return polling;
}