'use client'; import { useSearchParams } from 'next/navigation'; import { useEffect, useState, useCallback, Suspense } from 'react'; function StripePopupContent() { const searchParams = useSearchParams(); const orderId = searchParams.get('order_id') || ''; const amount = parseFloat(searchParams.get('amount') || '0') || 0; const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light'; const method = searchParams.get('method') || ''; const isDark = theme === 'dark'; const isAlipay = method === 'alipay'; // Sensitive data received via postMessage from parent, NOT from URL const [credentials, setCredentials] = useState<{ clientSecret: string; publishableKey: string; } | null>(null); const [stripeLoaded, setStripeLoaded] = useState(false); const [stripeSubmitting, setStripeSubmitting] = useState(false); const [stripeError, setStripeError] = useState(''); const [stripeSuccess, setStripeSuccess] = useState(false); const [stripeLib, setStripeLib] = useState<{ stripe: import('@stripe/stripe-js').Stripe; elements: import('@stripe/stripe-js').StripeElements; } | null>(null); const buildReturnUrl = useCallback(() => { const returnUrl = new URL(window.location.href); returnUrl.pathname = '/pay/result'; returnUrl.search = ''; returnUrl.searchParams.set('order_id', orderId); returnUrl.searchParams.set('status', 'success'); returnUrl.searchParams.set('popup', '1'); return returnUrl.toString(); }, [orderId]); // Listen for credentials from parent window via postMessage useEffect(() => { const handler = (event: MessageEvent) => { if (event.origin !== window.location.origin) return; if (event.data?.type !== 'STRIPE_POPUP_INIT') return; const { clientSecret, publishableKey } = event.data; if (clientSecret && publishableKey) { setCredentials({ clientSecret, publishableKey }); } }; window.addEventListener('message', handler); // Signal parent that popup is ready to receive data if (window.opener) { window.opener.postMessage({ type: 'STRIPE_POPUP_READY' }, window.location.origin); } return () => window.removeEventListener('message', handler); }, []); // Initialize Stripe once credentials are received useEffect(() => { if (!credentials) return; let cancelled = false; const { clientSecret, publishableKey } = credentials; import('@stripe/stripe-js').then(({ loadStripe }) => { loadStripe(publishableKey).then((stripe) => { if (cancelled || !stripe) { if (!cancelled) { setStripeError('支付组件加载失败,请关闭窗口重试'); setStripeLoaded(true); } return; } if (isAlipay) { // Alipay: confirm directly and redirect, no Payment Element needed stripe .confirmAlipayPayment(clientSecret, { return_url: buildReturnUrl(), }) .then((result) => { if (cancelled) return; if (result.error) { setStripeError(result.error.message || '支付失败,请重试'); setStripeLoaded(true); } // If no error, the page has already been redirected }); return; } // Fallback: create Elements for Payment Element flow const elements = stripe.elements({ clientSecret, appearance: { theme: isDark ? 'night' : 'stripe', variables: { borderRadius: '8px' }, }, }); setStripeLib({ stripe, elements }); setStripeLoaded(true); }); }); return () => { cancelled = true; }; }, [credentials, isDark, isAlipay, buildReturnUrl]); // Mount Payment Element (only for non-alipay methods) const stripeContainerRef = useCallback( (node: HTMLDivElement | null) => { if (!node || !stripeLib) return; const existing = stripeLib.elements.getElement('payment'); if (existing) { existing.mount(node); } else { stripeLib.elements.create('payment', { layout: 'tabs' }).mount(node); } }, [stripeLib], ); const handleSubmit = async () => { if (!stripeLib || stripeSubmitting) return; setStripeSubmitting(true); setStripeError(''); const { stripe, elements } = stripeLib; const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: buildReturnUrl(), }, redirect: 'if_required', }); if (error) { setStripeError(error.message || '支付失败,请重试'); setStripeSubmitting(false); } else { setStripeSuccess(true); setStripeSubmitting(false); } }; // Auto-close after success useEffect(() => { if (!stripeSuccess) return; const timer = setTimeout(() => { window.close(); }, 2000); return () => clearTimeout(timer); }, [stripeSuccess]); // Waiting for credentials from parent if (!credentials) { return (
订单号: {orderId}
订单号: {orderId}
支付成功,窗口即将自动关闭...