'use client'; import { useSearchParams } from 'next/navigation'; import { useState, useEffect, Suspense } from 'react'; import PaymentForm from '@/components/PaymentForm'; import PaymentQRCode from '@/components/PaymentQRCode'; import OrderStatus from '@/components/OrderStatus'; import PayPageLayout from '@/components/PayPageLayout'; import MobileOrderList from '@/components/MobileOrderList'; import { detectDeviceIsMobile, type UserInfo, type MyOrder } from '@/lib/pay-utils'; import type { MethodLimitInfo } from '@/components/PaymentForm'; interface OrderResult { orderId: string; amount: number; status: string; paymentType: 'alipay' | 'wxpay' | 'stripe'; payUrl?: string | null; qrCode?: string | null; checkoutUrl?: string | null; expiresAt: string; } interface AppConfig { enabledPaymentTypes: string[]; minAmount: number; maxAmount: number; maxDailyAmount: number; methodLimits?: Record; helpImageUrl?: string | null; helpText?: string | null; } function PayContent() { const searchParams = useSearchParams(); const userId = Number(searchParams.get('user_id')); const token = (searchParams.get('token') || '').trim(); const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light'; const uiMode = searchParams.get('ui_mode') || 'standalone'; const tab = searchParams.get('tab'); const isDark = theme === 'dark'; const [isIframeContext, setIsIframeContext] = useState(true); const [isMobile, setIsMobile] = useState(false); const [step, setStep] = useState<'form' | 'paying' | 'result'>('form'); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [orderResult, setOrderResult] = useState(null); const [finalStatus, setFinalStatus] = useState(''); const [userInfo, setUserInfo] = useState(null); const [resolvedUserId, setResolvedUserId] = useState(null); const [myOrders, setMyOrders] = useState([]); const [ordersPage, setOrdersPage] = useState(1); const [ordersHasMore, setOrdersHasMore] = useState(false); const [ordersLoadingMore, setOrdersLoadingMore] = useState(false); const [activeMobileTab, setActiveMobileTab] = useState<'pay' | 'orders'>('pay'); const [config, setConfig] = useState({ enabledPaymentTypes: ['alipay', 'wxpay', 'stripe'], minAmount: 1, maxAmount: 1000, maxDailyAmount: 0, }); const [userNotFound, setUserNotFound] = useState(false); const effectiveUserId = resolvedUserId || userId; const isEmbedded = uiMode === 'embedded' && isIframeContext; const hasToken = token.length > 0; const helpImageUrl = (config.helpImageUrl || '').trim(); const helpText = (config.helpText || '').trim(); const hasHelpContent = Boolean(helpImageUrl || helpText); useEffect(() => { if (typeof window === 'undefined') return; setIsIframeContext(window.self !== window.top); setIsMobile(detectDeviceIsMobile()); }, []); useEffect(() => { if (!isMobile || step !== 'form') return; if (tab === 'orders') { setActiveMobileTab('orders'); return; } setActiveMobileTab('pay'); }, [isMobile, step, tab]); const loadUserAndOrders = async () => { if (!userId || Number.isNaN(userId) || userId <= 0) return; setUserNotFound(false); try { // 始终获取服务端配置(不含隐私信息) const cfgRes = await fetch(`/api/user?user_id=${userId}`); if (cfgRes.ok) { const cfgData = await cfgRes.json(); if (cfgData.config) { setConfig({ enabledPaymentTypes: cfgData.config.enabledPaymentTypes ?? ['alipay', 'wxpay'], minAmount: cfgData.config.minAmount ?? 1, maxAmount: cfgData.config.maxAmount ?? 1000, maxDailyAmount: cfgData.config.maxDailyAmount ?? 0, methodLimits: cfgData.config.methodLimits, helpImageUrl: cfgData.config.helpImageUrl ?? null, helpText: cfgData.config.helpText ?? null, }); } } else if (cfgRes.status === 404) { setUserNotFound(true); return; } // 有 token 时才尝试获取用户详情和订单 if (token) { const meRes = await fetch(`/api/orders/my?token=${encodeURIComponent(token)}`); if (meRes.ok) { const meData = await meRes.json(); const meUser = meData.user || {}; const meId = Number(meUser.id); if (Number.isInteger(meId) && meId > 0) { setResolvedUserId(meId); } setUserInfo({ id: Number.isInteger(meId) && meId > 0 ? meId : userId, username: (typeof meUser.displayName === 'string' && meUser.displayName.trim()) || (typeof meUser.username === 'string' && meUser.username.trim()) || `用户 #${userId}`, balance: typeof meUser.balance === 'number' ? meUser.balance : undefined, }); if (Array.isArray(meData.orders)) { setMyOrders(meData.orders); setOrdersPage(1); setOrdersHasMore((meData.total_pages ?? 1) > 1); } else { setMyOrders([]); setOrdersPage(1); setOrdersHasMore(false); } return; } } // 无 token 或 token 失效:只显示用户 ID,不展示隐私信息(不显示余额) setUserInfo({ id: userId, username: `用户 #${userId}` }); setMyOrders([]); setOrdersPage(1); setOrdersHasMore(false); } catch { // ignore and keep page usable } }; const loadMoreOrders = async () => { if (!token || ordersLoadingMore || !ordersHasMore) return; const nextPage = ordersPage + 1; setOrdersLoadingMore(true); try { const res = await fetch(`/api/orders/my?token=${encodeURIComponent(token)}&page=${nextPage}&page_size=20`); if (!res.ok) return; const data = await res.json(); if (Array.isArray(data.orders) && data.orders.length > 0) { setMyOrders((prev) => [...prev, ...data.orders]); setOrdersPage(nextPage); setOrdersHasMore(nextPage < (data.total_pages ?? 1)); } else { setOrdersHasMore(false); } } catch { // ignore } finally { setOrdersLoadingMore(false); } }; useEffect(() => { loadUserAndOrders(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [userId, token]); if (!effectiveUserId || Number.isNaN(effectiveUserId) || effectiveUserId <= 0) { return (

无效的用户 ID

请从 Sub2API 平台正确访问充值页面

); } if (userNotFound) { return (

用户不存在

请检查链接是否正确,或联系管理员

); } const buildScopedUrl = (path: string, forceOrdersTab = false) => { const params = new URLSearchParams(); if (effectiveUserId) params.set('user_id', String(effectiveUserId)); if (token) params.set('token', token); params.set('theme', theme); params.set('ui_mode', uiMode); if (forceOrdersTab) params.set('tab', 'orders'); return `${path}?${params.toString()}`; }; const pcOrdersUrl = buildScopedUrl('/pay/orders'); const mobileOrdersUrl = buildScopedUrl('/pay', true); const ordersUrl = isMobile ? mobileOrdersUrl : pcOrdersUrl; const handleSubmit = async (amount: number, paymentType: string) => { setLoading(true); setError(''); try { const res = await fetch('/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: effectiveUserId, amount, payment_type: paymentType, }), }); const data = await res.json(); if (!res.ok) { const codeMessages: Record = { USER_INACTIVE: '账户已被禁用,无法充值,请联系管理员', TOO_MANY_PENDING: '您有过多待支付订单,请先完成或取消现有订单后再试', USER_NOT_FOUND: '用户不存在,请检查链接是否正确', DAILY_LIMIT_EXCEEDED: data.error, METHOD_DAILY_LIMIT_EXCEEDED: data.error, PAYMENT_GATEWAY_ERROR: data.error, }; setError(codeMessages[data.code] || data.error || '创建订单失败'); return; } setOrderResult({ orderId: data.orderId, amount: data.amount, status: data.status, paymentType: data.paymentType || paymentType, payUrl: data.payUrl, qrCode: data.qrCode, checkoutUrl: data.checkoutUrl, expiresAt: data.expiresAt, }); setStep('paying'); } catch { setError('网络错误,请稍后重试'); } finally { setLoading(false); } }; const handleStatusChange = (status: string) => { setFinalStatus(status); setStep('result'); if (isMobile) { setActiveMobileTab('orders'); } }; const handleBack = () => { setStep('form'); setOrderResult(null); setFinalStatus(''); setError(''); }; useEffect(() => { if (step !== 'result' || finalStatus !== 'COMPLETED') return; // 立即在后台刷新余额,2.2s 显示结果页后再切回表单(届时余额已更新) loadUserAndOrders(); const timer = setTimeout(() => { setStep('form'); setOrderResult(null); setFinalStatus(''); setError(''); }, 2200); return () => clearTimeout(timer); // eslint-disable-next-line react-hooks/exhaustive-deps }, [step, finalStatus]); return ( 我的订单 ) : undefined} > {error && (
{error}
)} {step === 'form' && isMobile && (
)} {step === 'form' && ( <> {isMobile ? ( activeMobileTab === 'pay' ? ( ) : ( ) ) : (
支付说明
  • 订单完成后会自动到账
  • 如需历史记录请查看"我的订单"
  • {config.maxDailyAmount > 0 && (
  • 每日最大充值 ¥{config.maxDailyAmount.toFixed(2)}
  • )} {!hasToken &&
  • 当前链接无 token,订单查询受限
  • }
{hasHelpContent && (
Support
{helpImageUrl && ( help )} {helpText && (

{helpText}

)}
)}
)} )} {step === 'paying' && orderResult && ( )} {step === 'result' && ( )}
); } export default function PayPage() { return (
加载中...
} >
); }