Stripe:在 nextjs 中找不到元素上下文错误

Stripe : Could not find Elements context error in nextjs

我在 nextjs 12 中使用 stripe following 包

import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";

它在开发模式下运行良好,但在生产构建时抛出此错误

Error: Could not find Elements context; You need to wrap the part of your app that calls useStripe() in an <Elements> provider.

我将 Elements 用作父容器,将 Elements 中的 Checkoutform 用作子容器

import React from 'react'
import { useAppContext } from '@/context/AppContext';
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
import Checkoutform from './Checkoutform';
import AppLoader from '@/components/common/Loader';

const Payment = () => {
    const { state } = useAppContext();
    const { user, planType } = state;
    const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY);
    const options = {
        clientSecret: planType.orderData.client_secret,
        appearance: {
            theme: 'stripe',
        }
    };

    return (
        options.clientSecret && options.clientSecret.length > 0 ? (
            <Elements stripe={stripePromise} options={options} >
                <Checkoutform userData={user} planType={planType} />
            </Elements >
        ) : (
            <AppLoader />
        )
    )
}


export default Payment

Checkoutform.js

import React, { useEffect, useState } from "react";
import {
    PaymentElement,
    useStripe,
    useElements
} from "@stripe/react-stripe-js";
import AppLoader from "@/components/common/Loader";
import { toast } from "react-toastify";
import { baseURL } from "@/constants/utils/universal";

export default function Checkoutform(props) {
    const { userData, planType } = props;
    const stripe = useStripe();
    const elements = useElements();
    const [message, setMessage] = useState(null);
    const [isLoading, setIsLoading] = useState(false);
    const [isInitialized, setIsInitialized] = useState(true);
    const redirectUrl = baseURL + "/pricing/payment/success";


    useEffect(() => {
        if (typeof window === "undefined" && !stripe) {
            return;
        }

        const clientSecret = new URLSearchParams(window.location.search).get(
            "payment_intent_client_secret"
        );

        if (!clientSecret) {
            return;
        }
        stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
            switch (paymentIntent.status) {
                case "succeeded":
                    toast.success("Thankyou!, Payment Successful, please check your email for your receipt");
                    setMessage("Payment succeeded!");
                    break;
                case "processing":
                    setMessage("Your payment is processing.");
                    break;
                case "requires_payment_method":
                    setMessage("Your payment was not successful, please try again.");
                    break;
                default:
                    setMessage("Something went wrong.");
                    break;
            }
        });
    }, [stripe]);

    useEffect(() => {
        setTimeout(() => {
            if (typeof window !== "undefined") {
                setIsInitialized(false);
            }
        }, 1000);
    }, [])

    const handleSubmit = async (e) => {
        e.preventDefault();

        if (!stripe || !elements) {
            return;
        }

        setIsLoading(true);

        const { error } = await stripe.confirmPayment({
            elements,
            confirmParams: {
                return_url: redirectUrl,
            },
        });

        if (error.type === "card_error" || error.type === "validation_error") {
            toast.error("Oops! some error occurred, please try again");
            setMessage(error.message);
        } else {
            toast.error("Oops! some error occurred, please try again");
            setMessage("An unexpected error occured.");
        }

        setIsLoading(false);
    };

    return (
        <div className='container my-5 pt-5'>
            <div className='row'>
                <div className='col-md-6 col-12 mx-auto'>
                    <div className='card shadow-sm p-5'>
                        {
                            !isInitialized ?
                                <form id="payment-form" onSubmit={handleSubmit}>
                                    <PaymentElement id="payment-element" className="mb-5" />
                                    <div className="mt-3 d-grid">
                                        <button disabled={isLoading || !stripe || !elements} id="submit" className="btn app-cta">
                                            <span id="button-text">
                                                {isLoading ? "processing please wait..." : "Pay now"}
                                            </span>
                                        </button>
                                    </div>
                                    {/* Show any error or success messages */}
                                    {message && <div id="payment-message" className='my-2 small fw-light text-danger'>{message}</div>}
                                </form>
                                :
                                <AppLoader />
                        }
                    </div>
                </div>
            </div>
        </div>
    );
}

请帮助我我做错了什么或者我必须为生产构建添加什么

Jonathan Steele 是对的,如果您使用 checkoutform.js 作为组件,那么请确保将其保存在 components 文件夹中,该文件夹应该位于 pages 文件夹之外。因为如果它在 pages 文件夹内,那么在构建期间 nextjs 将尝试 pre-render 它,将其视为一个页面,这会给你这个错误