Vercel+Supabase+Stripe环境clientSecret无法传递至前端排查求助
问题:Stripe clientSecret无法传递至前端,页面持续显示Loading...
页面无报错信息,但始终卡在“Loading...”状态,核心原因是clientSecret无法从Stripe经后端正确传递到前端。已确认页面加载处理逻辑正常,且.env.local配置无误。
当前排查方向:
- 验证后端是否从Stripe正确获取到clientSecret
- 检查PaymentsPage组件中的useEffect钩子是否存在问题
前端代码
"use client" import { loadStripe } from '@stripe/stripe-js'; import { Elements, useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js'; import axios from 'axios'; import React, { useState, useEffect } from 'react'; const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || ''); // Define the packages const packages = [ { id: 'basic-001', name: 'Basic Package', description: 'Basic service offering.' }, { id: 'standard-002', name: 'Standard Package', description: 'Enhanced features included.' }, { id: 'premium-003', name: 'Premium Package', description: 'All features plus premium support.' }, { id: 'exclusive-004', name: 'Exclusive Package', description: 'All premium features with exclusive benefits.' } ]; const PaymentsPage = () => { const [clientSecret, setClientSecret] = useState<string | undefined>(undefined); // Changed from null to undefined const [selectedPackage, setSelectedPackage] = useState(packages[0].id); // Moved up here useEffect(() => { axios.post('/api/checkout_sessions', { packageId: selectedPackage }) .then(response => { setClientSecret(response.data.clientSecret); }) .catch(error => { console.error('Error:', error); }); }, [selectedPackage]); // Now depends on selectedPackage return ( clientSecret ? ( <Elements stripe={stripePromise} options={{ clientSecret }}> <CheckoutForm selectedPackage={selectedPackage} setSelectedPackage={setSelectedPackage} /> </Elements> ) : ( <div>Loading...</div> ) ); }; type CheckoutFormProps = { selectedPackage: string; setSelectedPackage: React.Dispatch<React.SetStateAction<string>>; }; const CheckoutForm = ({ selectedPackage, setSelectedPackage }: CheckoutFormProps) => { const stripe = useStripe(); const elements = useElements(); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); setIsLoading(true); if (!stripe || !elements) { setIsLoading(false); return; } try { const response = await axios.post('/api/checkout_sessions', { packageId: selectedPackage }); const clientSecret = response.data.clientSecret; const { error } = await stripe.confirmCardPayment(clientSecret); if (error) { console.log('[error]', error); } else { // Payment processed } } catch (error) { console.error('Error:', error); } setIsLoading(false); }; return ( <form onSubmit={handleSubmit}> <div> {packages.map((pkg) => ( <div key={pkg.id}> <input type="radio" id={pkg.id} name="package" value={pkg.id} checked={selectedPackage === pkg.id} onChange={(e) => setSelectedPackage(e.target.value)} /> <label htmlFor={pkg.id}>{pkg.name}: {pkg.description}</label> </div> ))} </div> <PaymentElement /> <button type="submit" disabled={!stripe || !elements || isLoading}> {isLoading ? 'Processing…' : 'Pay'} </button> </form> ); }; export default PaymentsPage;
后端代码
import { NextApiRequest, NextApiResponse } from 'next'; import Stripe from 'stripe'; const stripeSecretKey = process.env.STRIPE_SECRET_KEY; if (!stripeSecretKey) { throw new Error('The STRIPE_SECRET_KEY environment variable is not set.'); } const stripe = new Stripe(stripeSecretKey, { apiVersion: '2023-10-16', }); export default async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== 'POST') { res.setHeader('Allow', 'POST'); res.status(405).end('Method Not Allowed'); return; } try { const { packageId } = req.body; const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [{ price: packageId, quantity: 1, }], mode: 'payment', success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/canceled`, }); // Here we obtain the PaymentIntent's clientSecret from the session if (typeof session.payment_intent === 'string') { const paymentIntent = await stripe.paymentIntents.retrieve(session.payment_intent as string); res.status(200).json({ sessionId: session.id, clientSecret: paymentIntent.client_secret }); } else { // Handle the case where payment_intent is null (e.g., log an error or throw an exception) res.status(500).json({ error: 'PaymentIntent is null' }); } } catch (err: unknown) { if (err instanceof Error) { // Now we check if it's a Stripe error if ("statusCode" in err && "message" in err) { // Here we're assuming that if the error has statusCode, it's a Stripe error. // We cast the error type to any to access the statusCode and message properties. res.status((err as any).statusCode || 500).json({ error: err.message }); } else { // If it's not a Stripe error, we return a generic error response res.status(500).json({ error: 'An unexpected error occurred' }); } } else { // If it's not an Error instance, handle accordingly res.status(500).json({ error: 'An unexpected error occurred' }); } } };
目前无法确定是后端未从Stripe获取到clientSecret,还是传递到前端环节出现问题,请求协助排查,已陷入停滞。
内容的提问来源于stack exchange,提问作者filthysasuke
相关产品推荐
相关产品推荐

