You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React useEffect在Stripe success_url重定向后未执行的问题

问题:Stripe支付成功页useEffect未执行,API调用无法触发

需求与问题

需求:用户被重定向到Stripe的success_url后,调用Stripe API获取会话确认支付状态;确认支付完成后,调用自研Java服务器API将支付状态改为成功并清空用户购物车。
问题:重定向到页面后,useEffect未执行,无法触发上述API调用;尝试将API调用放在useEffect外,同样无法触发。

现有代码

页面组件代码

import Navigationbar from "../../Component/NavBar";
import React, {useEffect, useState} from "react";
import tick from "../../images/check.png";
import instagram from "../../images/instagram.png";
import twitter from "../../images/twitter.png";
import wechat from "../../images/wechat.png";
import tiktok from "../../images/tiktok.png";
import email from "../../images/gmail.png";
import "./style.css";
import {getCheckoutSessionDetails} from "../../../resource/CheckoutSessionResource";
import {changeTransactionStatusToFinished, getTransactionDetailData} from "../../../resource/TransactionDetailResource";
import {TransactionDetailData} from "../../../data/TransactionDetailData";
import {useParams} from "react-router-dom";
import LoadingSpinner from "../../Component/LoadingSpinner";

type params={
    tid: string;
}

export default function TransactionCompletedPage(){
    const domain=window.location.href;
    const sessionId=domain.split("=")[1];
    const params=useParams<params>();
    const [paymentStatus,setPaymentStatus]=useState<boolean|undefined>(undefined);
    const [isRendered,setIsRendered]=useState<boolean>(true);

    if(paymentStatus) {
          changeTransactionStatusToFinished(parseInt(params.tid as string))
    }

//The following API is not called...
    useEffect(()=>(
        getCheckoutSessionDetails(params.tid as string,sessionId,setPaymentStatus)
    ) , [] )

        return(
            <>
                <Navigationbar/>
                <div className={"transaction-process-container"}>
                    <div className={"transaction-process-step-one-checkout"}>
                        <div className={"transaction-process"}>
                            <div className={"transaction-not-in-process-icon"}>1</div>
                        </div>
                        <div className={"transaction-process-step-one-name"}>Checkout</div>
                    </div>
                    <div className={"division-line"}></div>
                    <div className={"transaction-process-step-two-payment"}>
                        <div className={"transaction-process"}>
                            <div className={"transaction-not-in-process-icon"}>2</div>
                        </div>
                        <div className={"transaction-process-step-three-name"}>Payment</div>
                    </div>
                    <div className={"division-line"}></div>
                    <div className={"transaction-process-step-three-completion"}>
                        <div className={"transaction-process"}>
                            <div className={"transaction-in-process-icon"}>3</div>
                        </div>
                        <div className={"transaction-process-step-four-name"}>Transaction Completed</div>
                    </div>
                </div>
                <img className={"tick-image"} src={tick}/>
                <h3 className={"payment-successful-message"}>PAYMENT SUCCESSFUL</h3>
                <div className={"email-message"}> Thank you for patronizing our shop!
                    <br/>A confirmation email has been sent to you via the email address that you used to create this account in our platform
                </div>
                <div className={"social-media-logos-and-customer-service-container"}>
                    <div className={"social-media-container"}>
                        <div className={"social-media"}>
                            Follow Us on Social Media
                        </div>
                        <div className={"social-media-logos-container"}>
                            <img src={instagram} className={"social-media-logos"}/>
                            <img src={tiktok} className={"social-media-logos"}/>
                            <img src={twitter} className={"social-media-logos"}/>
                            <img src={wechat} className={"social-media-logos"}/>
                        </div>
                    </div>
                    <div className={"customer-service-container"}>
                        <div className={"social-media"}>Customer Service</div>
                        <div className={"social-media-logos-container"}>
                            <img src={email} className={"social-media-logos"}/>
                            <img src={wechat} className={"social-media-logos"}/>
                        </div>
                    </div>
                </div>
            </>
        )
}

API调用代码

获取Stripe会话详情

export const getCheckoutSessionDetails=(tid:string,sessionId:string,setPaymentStatus:(status:boolean)=>void)=> {
    firebaseAuthServiceGetAccessToken()?.then((token)=>{
        return axios.get(`http://localhost:3001/transaction/success?session_id=${sessionId}`)
    })
        .then(function (response) {
            console.log(response);
            if(response.data.payment_status==="paid"){
                setPaymentStatus(true);
            }else if(response.data.payment_status==="unpaid"){
                setPaymentStatus(false);
            }
        })
        .catch(function (error) {
            console.log(error);
        })
}

更新交易状态为完成

export const changeTransactionStatusToFinished=(tid:number)=>{
    firebaseAuthServiceGetAccessToken()?.then((token)=> {
        console.log(token);
        return axios.patch(
            `http://localhost:8080/transaction/${tid}/finish`, {}, {headers: {Authorization: `Bearer ${token}`}})
    }).then((response:AxiosResponse<TransactionDetailData>)=>{
        console.log(response);
    }).catch((response)=>{
        console.log(response);
    })
}

问题分析与修复方案

核心问题点

  1. useEffect依赖缺失:useEffect的依赖数组为空,但内部使用了params.tid和sessionId,这两个值在组件初始化时可能还未完全获取,导致effect无法触发或拿到无效值。
  2. 组件顶层的条件调用错误:直接在组件渲染流程中写if(paymentStatus) { ... }会导致无限重渲染——每次paymentStatus更新都会触发组件重新渲染,进而重复调用API,甚至可能阻塞正常渲染流程。
  3. sessionId解析不可靠:用字符串分割window.location.href获取sessionId,若URL中存在多个=符号会导致解析错误。
  4. useEffect回调写法错误:effect回调返回了getCheckoutSessionDetails的执行结果,但该函数是异步函数,effect的返回值应为清理函数,此写法会导致潜在的内存泄漏。

修复后的代码

页面组件

import Navigationbar from "../../Component/NavBar";
import React, {useEffect, useState} from "react";
import tick from "../../images/check.png";
import instagram from "../../images/instagram.png";
import twitter from "../../images/twitter.png";
import wechat from "../../images/wechat.png";
import tiktok from "../../images/tiktok.png";
import email from "../../images/gmail.png";
import "./style.css";
import {getCheckoutSessionDetails} from "../../../resource/CheckoutSessionResource";
import {changeTransactionStatusToFinished} from "../../../resource/TransactionDetailResource";
import {useParams} from "react-router-dom";
import LoadingSpinner from "../../Component/LoadingSpinner";

type params = {
    tid: string;
}

export default function TransactionCompletedPage() {
    // 用URLSearchParams可靠获取sessionId
    const searchParams = new URLSearchParams(window.location.search);
    const sessionId = searchParams.get('session_id');
    const params = useParams<params>();
    const [paymentStatus, setPaymentStatus] = useState<boolean | undefined>(undefined);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState<string | null>(null);

    // 监听paymentStatus变化,仅在状态为true时调用一次更新API
    useEffect(() => {
        if (paymentStatus === true && params.tid) {
            changeTransactionStatusToFinished(parseInt(params.tid))
                .then(() => {
                    // 这里添加清空购物车的逻辑
                    // clearCart();
                })
                .catch(err => {
                    setError('更新交易状态失败');
                    console.error(err);
                });
        }
    }, [paymentStatus, params.tid]);

    // 获取Stripe会话详情的effect,添加正确依赖
    useEffect(() => {
        let isMounted = true;

        const fetchSessionDetails = async () => {
            if (!sessionId || !params.tid) {
                setError('缺少必要参数');
                setIsLoading(false);
                return;
            }
            try {
                await getCheckoutSessionDetails(params.tid, sessionId, (status) => {
                    if (isMounted) {
                        setPaymentStatus(status);
                    }
                });
            } catch (err) {
                setError('获取支付会话失败');
                console.error(err);
            } finally {
                if (isMounted) {
                    setIsLoading(false);
                }
            }
        };

        fetchSessionDetails();

        // 清理函数,防止组件卸载后更新状态
        return () => {
            isMounted = false;
        };
    }, [sessionId, params.tid]);

    // 加载状态下显示 spinner
    if (isLoading) {
        return <LoadingSpinner />;
    }

    // 错误状态提示
    if (error) {
        return (
            <>
                <Navigationbar />
                <div className="error-container">
                    <h3>出错了:{error}</h3>
                    <button onClick={() => window.location.reload()}>重试</button>
                </div>
            </>
        );
    }

    // 支付未成功的提示
    if (paymentStatus === false) {
        return (
            <>
                <Navigationbar />
                <div className="payment-failed-container">
                    <h3>支付未完成</h3>
                    <p>请检查支付状态或重新发起支付</p>
                </div>
            </>
        );
    }

    return (
        <>
            <Navigationbar />
            <div className={"transaction-process-container"}>
                <div className={"transaction-process-step-one-checkout"}>
                    <div className={"transaction-process"}>
                        <div className={"transaction-not-in-process-icon"}>1</div>
                    </div>
                    <div className={"transaction-process-step-one-name"}>Checkout</div>
                </div>
                <div className={"division-line"}></div>
                <div className={"transaction-process-step-two-payment"}>
                    <div className={"transaction-process"}>
                        <div className={"transaction-not-in-process-icon"}>2</div>
                    </div>
                    <div className={"transaction-process-step-three-name"}>Payment</div>
                </div>
                <div className={"division-line"}></div>
                <div className={"transaction-process-step-three-completion"}>
                    <div className={"transaction-process"}>
                        <div className={"transaction-in-process-icon"}>3</div>
                    </div>
                    <div className={"transaction-process-step-four-name"}>Transaction Completed</div>
                </div>
            </div>
            <img className={"tick-image"} src={tick} />
            <h3 className={"payment-successful-message"}>PAYMENT SUCCESSFUL</h3>
            <div className={"email-message"}> Thank you for patronizing our shop!
                <br />A confirmation email has been sent to you via the email address that you used to create this account in our platform
            </div>
            <div className={"social-media-logos-and-customer-service-container"}>
                <div className={"social-media-container"}>
                    <div className={"social-media"}>
                        Follow Us on Social Media
                    </div>
                    <div className={"social-media-logos-container"}>
                        <img src={instagram} className={"social-media-logos"} />
                        <img src={tiktok} className={"social-media-logos"} />
                        <img src={twitter} className={"social-media-logos"} />
                        <img src={wechat} className={"social-media-logos"} />
                    </div>
                </div>
                <div className={"customer-service-container"}>
                    <div className={"social-media"}>Customer Service</div>
                    <div className={"social-media-logos-container"}>
                        <img src={email} className={"social-media-logos"} />
                        <img src={wechat} className={"social-media-logos"} />
                    </div>
                </div>
            </div>
        </>
    )
}

优化后的API方法(可选)

调整getCheckoutSessionDetails,确保token获取成功后再发起请求,并返回Promise方便外部处理:

export const getCheckoutSessionDetails = async (tid: string, sessionId: string, setPaymentStatus: (status: boolean) => void) => {
    try {
        const token = await firebaseAuthServiceGetAccessToken();
        if (!token) {
            throw new Error('未获取到用户授权token');
        }
        const response = await axios.get(`http://localhost:3001/transaction/success?session_id=${sessionId}`, {
            headers: { Authorization: `Bearer ${token}` }
        });
        console.log(response);
        if (response.data.payment_status === "paid") {
            setPaymentStatus(true);
        } else if (response.data.payment_status === "unpaid") {
            setPaymentStatus(false);
        }
    } catch (error) {
        console.error(error);
        throw error; // 抛出错误让上层处理
    }
}

额外说明

  • 添加了加载状态、错误状态和支付未成功的UI反馈,提升用户体验。
  • 使用isMounted标志防止组件卸载后更新状态,避免内存泄漏。
  • 用URLSearchParams替代字符串分割,确保sessionId解析准确。
  • 将更新交易状态的逻辑移到useEffect中,仅在paymentStatus变为true且params.tid存在时执行一次,避免重复请求和无限渲染。

内容的提问来源于stack exchange,提问作者Ben Chu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:15:47