NextJS 14中Stripe支付成功后无法删除Cookie问题
在NextJS 14项目中,用户通过Stripe完成支付后会被重定向到src/app/client/payment/payment-success/page.tsx页面。该页面会检查envoi数据是否包含trackingNumber、qrCodeUrl且paid为真、simulationStatus为COMPLETED,若满足条件则调用deleteSimulationCookie函数删除模拟Cookie。此函数通过请求后端API路由src/app/api/v1/simulations/delete-cookies/route.ts来删除Cookie,在用户登出场景下可正常工作,但在Stripe支付成功重定向后却无法完成Cookie删除操作。
支付成功页面代码
// path: src/app/client/payment/payment-success/page.tsx 'use client'; import React, {useEffect, useState} from 'react'; import RequireAuth from "@/components/auth/RequireAuth"; import {getSimulationFromCookie} from "@/lib/simulationCookie"; import {toast} from "react-toastify"; import {getEnvoiById, updateEnvoiDatas} from '@/services/frontend-services/envoi/EnvoiService'; import {$Enums} from "@prisma/client"; import {checkAuthStatus} from "@/lib/auth"; import {useRouter} from "next/navigation"; import {deleteSimulationCookie} from '@/services/frontend-services/simulation/SimulationService'; import {DOMAIN} from "@/utils/constants"; import SimulationStatus = $Enums.SimulationStatus; const PaymentSuccessPage = () => { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); const [didUpdate, setDidUpdate] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false); const [userId, setUserId] = useState<string | null>(null); useEffect(() => { const checkAuth = async () => { setIsLoading(true); const authResult = await checkAuthStatus(false); setIsAuthenticated(authResult.isAuthenticated); if (authResult.isAuthenticated) { setUserId(authResult.userId || null); } setIsLoading(false); }; checkAuth(); }, [router]); useEffect(() => { const finalizePayment = async () => { const simulation = await getSimulationFromCookie(); if (!simulation) { toast.error("Erreur lors de la suppression de la simulation."); return; } // get envoi by id const envoi = await getEnvoiById(Number(simulation.id)); if (!envoi) { console.log("log ====> envoi not found from getEnvoiById in PaymentSuccessPage in path: src/app/client/payment/payment-success/page.tsx is : ", envoi); toast.error("Erreur lors de la suppression de l'envoi."); return; } console.log("log ====> envoi from getEnvoiById successful in PaymentSuccessPage with id:", envoi); if (envoi.trackingNumber && envoi.qrCodeUrl && envoi.paid && (envoi.simulationStatus === SimulationStatus.COMPLETED)) { // Delete cookies and redirect console.log("log ====> deleting simulation cookie..."); try { await deleteSimulationCookie(); console.log("log ====> Cookie deleted, redirecting..."); router.push(`${DOMAIN}/client/profile`); setDidUpdate(true); } catch (error) { console.error("Failed to delete cookie:", error); toast.error("Erreur lors de la suppression des cookies."); } return; } // 1. Update envoi data (generates tracking, QR code, etc.) const updatedEnvoi = await updateEnvoiDatas(Number(simulation.id)); if (!updatedEnvoi) { toast.error("Erreur lors de la mise à jour de l'envoi."); return; } if (!envoi.arrivalAgency?.id || !envoi.userId) { toast.error("Erreur lors de la mise à jour de l'agence."); return; } // delete cookies of the simulation and redirect to the profile page toast.success("Envoi mis à jour avec succès."); router.push(`${DOMAIN}/client/profile`); }; finalizePayment(); }, [router]); return ( <RequireAuth> <div className="flex flex-col items-center justify-center min-h-screen"> <h1 className="text-3xl font-bold text-green-600">Paiement Réussi !</h1> <p className="mt-4">Merci pour votre paiement. Votre transaction a été complétée avec succès.</p> </div> </RequireAuth> ); }; export default PaymentSuccessPage;
核心判断逻辑
if (envoi.trackingNumber && envoi.qrCodeUrl && envoi.paid && (envoi.simulationStatus === SimulationStatus.COMPLETED)) { // Delete cookies and redirect console.log("log ====> deleting simulation cookie..."); try { await deleteSimulationCookie(); console.log("log ====> Cookie deleted, redirecting..."); router.push(`${DOMAIN}/client/profile`); setDidUpdate(true); } catch (error) { console.error("Failed to delete cookie:", error); toast.error("Erreur lors de la suppression des cookies."); } return; }
删除Cookie的服务函数
/** * Delete the simulation cookie * @returns {Promise<void | null>} A Promise that resolves when the status is updated */ // path: src/services/frontend-services/simulation/SimulationService.ts export async function deleteSimulationCookie(): Promise<void | null> { try { await axios.get(`${API_DOMAIN}/simulations/delete-cookies`, { withCredentials: true }); } catch (error) { throw error; } }
后端API路由
// path: src/app/api/v1/simulations/delete-cookies/route.ts import {NextResponse} from "next/server"; import {errorHandler} from "@/utils/handelErrors"; import {cookies} from "next/headers"; import {DOMAIN} from "@/utils/constants"; export async function GET() { console.log("log ====> GET request reached at: /api/v1/simulations/delete-cookies"); try { const cookieName = process.env.SIMULATION_COOKIE_NAME; if (!cookieName) { return NextResponse.json( {message: "Server configuration error"}, {status: 500} ); } // Delete the simulation cookie with a correct path if (cookies().has(cookieName)) { cookies().delete(cookieName); } // Create response with CORS headers const response = NextResponse.json( {message: "Simulation cookies deleted successfully"}, {status: 200} ); response.headers.set('Access-Control-Allow-Origin', `${DOMAIN}`); response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); response.headers.set('Access-Control-Allow-Credentials', 'true'); return response; } catch (error) { return errorHandler("Internal server error", 500); } }
可能的原因及解决方案
1. 重定向时机过早,API请求未完成
虽然代码中使用了await等待删除请求完成,但Next.js路由跳转可能会提前卸载当前页面,导致请求被中断。可以添加组件挂载状态,确保仅在组件挂载时执行后续操作:
useEffect(() => { let isMounted = true; const finalizePayment = async () => { // ... 现有逻辑 try { await deleteSimulationCookie(); if (isMounted) { console.log("log ====> Cookie deleted, redirecting..."); router.push(`${DOMAIN}/client/profile`); setDidUpdate(true); } } catch (error) { if (isMounted) { console.error("Failed to delete cookie:", error); toast.error("Erreur lors de la suppression des cookies."); } } }; finalizePayment(); return () => { isMounted = false; }; // 组件卸载时标记为未挂载 }, [router]);
2. Cookie属性不匹配导致删除失败
Next.js的cookies().delete需要完全匹配Cookie的所有属性(路径、域名、SameSite等)才能成功删除。修改后端删除逻辑,明确指定Cookie的属性:
// 后端路由中修改删除逻辑 const cookieStore = cookies(); const existingCookie = cookieStore.get(cookieName); if (existingCookie) { cookieStore.delete({ name: cookieName, path: existingCookie.path || '/', domain: existingCookie.domain, secure: existingCookie.secure, sameSite: existingCookie.sameSite as 'strict' | 'lax' | 'none' }); }
3. 请求被浏览器拦截或CORS配置问题
检查API_DOMAIN是否与当前页面域名一致,确保axios请求的withCredentials: true正确传递Cookie。同时,后端CORS配置中可以尝试放宽Access-Control-Allow-Origin为当前域名(而非硬编码的DOMAIN),或者确认Stripe重定向后的页面域名在允许列表内。
4. 模拟Cookie未正确传递到API请求
在支付成功页面,先确认getSimulationFromCookie()能正确获取到Cookie值,再执行后续逻辑。可以在控制台打印simulation的值,验证Cookie是否存在。
内容的提问来源于stack exchange,提问作者Just Relax قناة هدو</think_never_used_51bce0c785ca2f68081bfa7d91973934>

