Laravel Inertia React集成Stripe支付遇上下文错误求解决方案
Laravel Inertia React 集成Stripe支付网关报错解决方案
报错信息
无法找到Elements上下文;您需要将调用useStripe()的应用部分包裹在
提供者中
问题场景
在Laravel + Inertia + React项目中集成Stripe订阅支付,点击「选择方案」按钮触发支付流程时出现上述报错,核心代码包含首页组件、定价卡片组件和后端控制器。
解决方案
1. 全局注入Stripe Elements上下文
useStripe()和useElements()必须在<Elements>组件的上下文内使用,因此需要在包含PricingCard的父组件中添加该提供者。
修改Welcome组件:
import Navbar from "@/Components/Wesite/Navbar"; import Carousal from "@/Components/Wesite/Carousel"; import Cards from "@/Components/Wesite/Cards"; import { Link, Head, usePage } from "@inertiajs/react"; import PricingCard from "@/Components/Wesite/Pricing"; import FooterBrand from "@/Components/Wesite/Footer"; import { useState } from "react"; // 新增Stripe依赖 import { loadStripe } from "@stripe/stripe-js"; import { Elements } from "@stripe/react-stripe-js"; // 替换为你的Stripe发布密钥 const stripePromise = loadStripe('pk_test_your_publishable_key_here'); export default function Welcome(props) { const { image, memberships } = usePage().props; const { class_3, class_4, class_5, phone, message, location } = image; const [membershipCards, setMemberShipCards] = useState(memberships); return ( <> <Head title="Home" /> <div className="h-screen bg-gray-900"> {/* 省略其他代码 */} <section className="w-full bg-gray-900"> <div className="text-center p-10"> <div className="uppercase text-theme-orange text-2xl font-[700]"> OUR PLAN </div> <div className="uppercase text-white text-[32px] font-[600]"> CHOOSE YOUR PRICING PLAN </div> </div> <div className="container mx-auto p-10"> <div className="w-full flex gap-3 flex-grow justify-evenly flex-wrap"> {/* 用Elements包裹所有PricingCard */} <Elements stripe={stripePromise}> {membershipCards.map((item, index) => ( <PricingCard key={index} id={item.id} name={item.name} price={item.price} description={item.description} duration={item.duration} /> ))} </Elements> </div> </div> </section> {/* 省略其他代码 */} </div> </> ); }
2. 优化前端支付逻辑
修正表单提交行为、添加加载状态、支持自定义持卡人姓名,提升用户体验:
修改PricingCard组件:
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js"; import axios from "axios"; import { Card } from "flowbite-react"; import { useState } from "react"; let PricingCard = ({ id, name, price, description, duration }) => { const stripe = useStripe(); const elements = useElements(); const [cardHolderName, setCardHolderName] = useState(''); const [loading, setLoading] = useState(false); const handleSubscription = async (e) => { e.preventDefault(); if (!stripe || !elements || !cardHolderName) return; setLoading(true); try { const { setupIntent, error } = await stripe.confirmCardSetup({ payment_method: { card: elements.getElement(CardElement), billing_details: { name: cardHolderName } }, }); if (error) throw new Error(error.message); const response = await axios.post("/member/buy-subscription", { payment_method: setupIntent.payment_method, price_id: id }); if (response.data.success) { alert("订阅成功!"); // 可添加页面跳转或状态更新逻辑 } else { throw new Error(response.data.error); } } catch (err) { alert(`操作失败: ${err.message}`); console.error(err); } finally { setLoading(false); } }; return ( <Card> <h5 className="mb-4 text-xl font-medium text-gray-500 dark:text-gray-400"> {name} </h5> <div className="flex items-baseline text-gray-900 dark:text-white"> <span className="text-3xl font-semibold">$</span> <span className="text-5xl font-extrabold tracking-tight">{price}</span> <span className="ml-1 text-xl font-normal text-gray-500 dark:text-gray-400"> /{duration} month </span> </div> <div className="my-7 space-y-5"> <div className="w-[300px]">{description}</div> </div> <form onSubmit={handleSubscription} className="space-y-4"> <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 持卡人姓名 </label> <input type="text" value={cardHolderName} onChange={(e) => setCardHolderName(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-theme-orange" required /> </div> <div> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> 银行卡信息 </label> <CardElement options={{ style: { base: { fontSize: "16px", color: "#424770" }, invalid: { color: "#9e2146" } } }} /> </div> <button className="inline-flex w-full justify-center rounded-lg bg-theme-orange px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-theme-orange/90 focus:outline-none focus:ring-4 focus:ring-cyan-200 dark:focus:ring-cyan-900" type="submit" disabled={!stripe || loading || !cardHolderName} > {loading ? "处理中..." : "选择方案"} </button> </form> </Card> ); }; export default PricingCard;
3. 修正后端订阅逻辑
Laravel Cashier创建订阅的参数格式有误,调整为正确的调用方式:
修改MemberController:
<?php namespace App\Http\Controllers\Web; use App\Http\Controllers\Controller; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class MemberController extends Controller { public function buySubScription(Request $request) { if (!Auth::check()) { return response()->json(['error' => '请先登录'], 401); } $request->validate([ 'price_id' => 'required|string', 'payment_method' => 'required|string', ]); $user = auth()->user(); try { // 关联支付方式到用户 $user->addPaymentMethod($request->payment_method); // 创建订阅 $subscription = $user->newSubscription('default', $request->price_id) ->create($request->payment_method); return response()->json(['success' => true, 'subscription' => $subscription]); } catch (Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } } }
4. 关键配置检查
- 确保已安装Laravel Cashier:
composer require laravel/cashier,并完成迁移和Stripe密钥配置 - 在
.env文件中添加Stripe密钥:STRIPE_KEY=pk_test_your_publishable_key STRIPE_SECRET=sk_test_your_secret_key - 确认
memberships数据中的id字段对应Stripe平台的价格ID(格式为price_xxx)
内容的提问来源于stack exchange,提问作者Hassan Khan
相关产品推荐
相关产品推荐

