"use client"; import { useEffect, useState, type FormEvent } from "react"; import { usePayStatusPoll } from "@/app/lib/usePayStatusPoll"; const COOKIE_NAME = "nomadvip_pay_order"; const DEFAULT_PASSWORD = "12345678"; 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 isAnonymousUserId(value: string | null | undefined): value is string { return !!value && /^user\d+$/.test(value); } function parseUserIdFromOrderId(orderId: string): string { if (!orderId?.includes("_order_")) return ""; const prefix = orderId.split("_order_")[0]; const parts = prefix.split("_"); return parts.length >= 2 ? parts.slice(0, -1).join("_") : ""; } function getOrderIdFromPage(): string | null { if (typeof window === "undefined") return null; const params = new URLSearchParams(window.location.search); const fromUrl = params.get("order_id") || params.get("out_trade_no") || params.get("orderId"); if (fromUrl?.trim()) return fromUrl.trim(); const raw = getCookie(COOKIE_NAME); if (!raw?.trim()) return null; return raw.split("|")[0]?.trim() || null; } function persistAnonymousVip(userId: string): void { if (typeof window === "undefined") return; localStorage.setItem("userId", userId); localStorage.setItem("memosAccount", userId); localStorage.setItem("paidType", "vip"); if (localStorage.getItem("emailLinked") !== "1" && !localStorage.getItem("userEmail")) { localStorage.setItem("userName", userId); } } async function confirmOrder(orderId: string, userId: string): Promise { for (let attempt = 0; attempt < 3; attempt++) { try { const res = await fetch("/api/pay/confirm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ order_id: orderId, user_id: userId }), }); const data = await res.json().catch(() => ({})); if (data?.ok) return true; } catch (error) { console.error("pay/confirm request error:", error); } if (attempt < 2) { await new Promise((resolve) => setTimeout(resolve, 1000)); } } return false; } function SuccessPanel({ anonymousUserId }: { anonymousUserId: string | null }) { const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!anonymousUserId || !isAnonymousUserId(anonymousUserId)) { const timer = window.setTimeout(() => { window.location.href = "/"; }, 600); return () => window.clearTimeout(timer); } }, [anonymousUserId]); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); const normalizedEmail = email.trim().toLowerCase(); if (!normalizedEmail || !anonymousUserId) return; setLoading(true); setError(null); try { const res = await fetch("/api/auth/link-email", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: normalizedEmail, password: DEFAULT_PASSWORD, anonymousUserId, }), }); const data = await res.json().catch(() => ({})); if (!res.ok || !data?.ok || !data?.token || !data?.record?.id) { setError(data?.error || "绑定邮箱失败"); return; } const { pbSaveAuth } = await import("@/app/lib/pocketbase"); pbSaveAuth(data.token, data.record); await fetch("/api/auth/sync-session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: data.token, record: data.record, anonymousUserId, }), credentials: "include", }); localStorage.setItem("memosAccount", anonymousUserId); localStorage.setItem("userId", data.record.id); localStorage.setItem("userEmail", data.record.email || normalizedEmail); localStorage.setItem("userName", data.record.email || normalizedEmail); localStorage.setItem("emailLinked", "1"); localStorage.setItem("paidType", "vip"); window.location.href = "/"; } catch (submitError) { console.error("link-email failed:", submitError); setError("绑定邮箱失败"); } finally { setLoading(false); } }; return (

支付成功

最后一步,绑定邮箱后再进入 VIP。后续可以在多个站点共用同一账号。

{isAnonymousUserId(anonymousUserId) ? (

默认密码为 {DEFAULT_PASSWORD}。绑定后会把当前匿名会员迁移到统一邮箱账号。

setEmail(event.target.value)} placeholder="your@email.com" required disabled={loading} className="mb-3 w-full rounded-lg border border-slate-300 px-4 py-2 text-slate-800" /> {error ?

{error}

: null}
) : ( <>

正在返回首页

{ event.preventDefault(); window.location.href = "/"; }} className="mt-8 inline-flex items-center gap-2 rounded-full bg-sky-500 px-8 py-3 font-semibold text-white transition-colors hover:bg-sky-600" > 返回首页 )}
); } export default function PaidPage() { const [status, setStatus] = useState< "pending" | "success" | "fail" | "confirm_fail" >("pending"); const [confirmedUserId, setConfirmedUserId] = useState(null); const orderIdFromUrlOrCookie = getOrderIdFromPage(); usePayStatusPoll( async (orderId) => { const finalOrderId = (orderId || orderIdFromUrlOrCookie || "").trim(); const candidateUserId = parseUserIdFromOrderId(finalOrderId) || localStorage.getItem("userId"); if (!finalOrderId || !candidateUserId) { setStatus("confirm_fail"); return; } const confirmed = await confirmOrder(finalOrderId, candidateUserId); if (confirmed) { persistAnonymousVip(candidateUserId); setConfirmedUserId(candidateUserId); } setStatus(confirmed ? "success" : "confirm_fail"); }, () => { window.location.href = "/"; }, orderIdFromUrlOrCookie ); useEffect(() => { const params = new URLSearchParams(window.location.search); if (params.get("fail") === "1") { setStatus("fail"); return; } if (params.get("paid") !== "1") { return; } const orderId = getOrderIdFromPage(); const candidateUserId = (orderId ? parseUserIdFromOrderId(orderId) : "") || localStorage.getItem("userId"); if (!orderId || !candidateUserId) { setStatus("confirm_fail"); return; } void (async () => { const confirmed = await confirmOrder(orderId, candidateUserId); if (confirmed) { persistAnonymousVip(candidateUserId); setConfirmedUserId(candidateUserId); } setStatus(confirmed ? "success" : "confirm_fail"); })(); }, []); if (status === "pending") { return (

正在确认支付结果

正在写入会员记录,请稍候。
支付完成后会继续进入邮箱绑定。

); } if (status === "success") { const anonymousUserId = confirmedUserId || (typeof window !== "undefined" ? localStorage.getItem("userId") : null); return ; } if (status === "confirm_fail") { return ( ); } return ( ); }