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 它,将其视为一个页面,这会给你这个错误
我在 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 它,将其视为一个页面,这会给你这个错误