React集成Stripe PaymentElement遇UI及支付回调问题求助
React集成Stripe的问题排查与解决方案
问题描述
- PaymentElement UI显示异常:按Stripe官方教程实现后,PaymentElement仅显示简陋界面,无法展示教程中的完整卡片及多支付方式UI。
- 支付流程异常:输入信息后跳转到错误页面;测试支付成功后,系统自动跳转至
return_url,未执行自定义成功回调代码,但Stripe后台显示支付已完成。
提供的代码
前端代码
StripePayment.jsx
import React, { Fragment, useEffect, useState } from "react"; import { Elements } from "@stripe/react-stripe-js"; import { loadStripe } from "@stripe/stripe-js"; import { useHttpClient } from "../../hooks/http-hook"; import Loader from "./Loader"; import ModalWindow from "./ModalWindow"; import CheckoutForm from "./CheckoutForm"; import { FiX } from "react-icons/fi"; const StripePayment = (props) => { const [stripePromise, setStripePromise] = useState(null); const [clientSecret, setClientSecret] = useState(null); const { sendRequest } = useHttpClient(); const closeHandler = () => { props.cancel(); }; useEffect(() => { const config = async () => { try { const responseData = await sendRequest(`payment/configure`); setStripePromise(loadStripe(responseData.publishableKey)); } catch (err) { console.log(err); } }; config(); }, []); useEffect(() => { const loadPayment = async () => { try { const responseData = await sendRequest( `payment/create-payment-intent`, "POST", JSON.stringify({ amount: 1, email: "***@abv.bg", }), { "Content-Type": "application/json", } ); setClientSecret(responseData.clientSecret); } catch (err) { console.log(err); } }; loadPayment(); }, []); const appearance = { theme: "stripe", }; const options = { clientSecret, appearance, }; return ( <ModalWindow static="static" show={true}> <div style={{ padding: "40px" }} className="center_section"> <h1>Payment</h1> <FiX className="x_icon" onClick={closeHandler} /> {stripePromise && clientSecret ? ( <Elements stripe={stripePromise} options={options}> <CheckoutForm clientSecret={clientSecret} onSuccess={props.onSuccess} /> </Elements> ) : ( <Loader /> )} </div> </ModalWindow> ); }; export default StripePayment;
CheckoutForm.jsx
import React, { useState } from "react"; import { useStripe, useElements, PaymentElement, } from "@stripe/react-stripe-js"; import { useDispatch } from "react-redux"; import { showError } from "../../redux/error"; const CheckoutForm = (props) => { const [isProcessing, setIsProcessing] = useState(false); const stripe = useStripe(); const elements = useElements(); const dispatch = useDispatch(); const paymentElementOptions = { layout: "tabs", }; const handleSubmit = async (event) => { event.preventDefault(); if (!stripe || !elements) { return; } setIsProcessing(true); const { error, paymentIntent } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}`, }, redirect: "if_required", }); //error if (error.type === "card_error" || error.type === "validation_error") { dispatch(showError(error.message)); setIsProcessing(false); return; } //success if (paymentIntent && paymentIntent.status === "succeeded") { alert("contracts"); setIsProcessing(false); } }; return ( <form id="payment-form" onSubmit={handleSubmit}> <PaymentElement options={paymentElementOptions} /> <button type="submit" disabled={isProcessing || !stripe || !elements} className="rn-button-style--2 btn-solid mt--40" > {isProcessing ? "Processing..." : "Pay now"} </button> </form> ); }; export default CheckoutForm;
后端代码
Controller
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY_TEST, { apiVersion: "2022-08-01", }); const getConfiguration = async (req, res, next) => { res .status(201) .json({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY_TEST }); }; const postPaymentIntent = async (req, res, next) => { const { amount, email } = req.body; if (!amount || !email) { return next(new HttpError("No amount or email set, please try again", 500)); } let paymentIntent; try { paymentIntent = await stripe.paymentIntents.create({ currency: "eur", amount: 1, receipt_email: email, automatic_payment_methods: { enabled: true }, }); } catch (err) { return next( new HttpError("Payment Form failed to load, please try again", 400) ); } res.status(201).json({ clientSecret: paymentIntent.client_secret }); };
Routes
paymentRouter.get("/configure", getConfiguration); paymentRouter.post("/create-payment-intent", postPaymentIntent);
解决方案
问题1:PaymentElement UI简陋的修复
原因
- 前端Stripe依赖版本与服务端API版本(2022-08-01)不兼容;
automatic_payment_methods未禁止跳转,导致部分支付方式被隐藏;- 配置了
layout: "tabs",该布局会简化UI展示。
修复步骤
- 更新前端Stripe依赖:
npm update @stripe/react-stripe-js @stripe/stripe-js - 修改CheckoutForm中的PaymentElement布局配置:
// 换成accordion布局或直接删除配置用默认样式 const paymentElementOptions = { layout: "accordion", }; - 服务端调整PaymentIntent创建配置,强制在页面内展示所有支付方式:
automatic_payment_methods: { enabled: true, allow_redirects: 'never' },
问题2:跳转错误与回调不执行的修复
原因
- 域名未加入Stripe后台允许列表,导致跳转时触发错误;
- 需跳转的支付方式完成后,前端
confirmPayment不会返回成功状态,必须在跳转后的页面重新验证支付结果。
修复步骤
- 配置Stripe允许域名:
登录Stripe后台,进入支付设置→域名,添加你的业务域名(包括本地开发的localhost:3000)。 - 创建支付成功页面并添加验证逻辑:
// PaymentSuccess.jsx import { loadStripe } from '@stripe/stripe-js'; import { useEffect } from 'react'; const PaymentSuccess = ({ onSuccess }) => { useEffect(() => { const verifyPayment = async () => { const stripe = await loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY); const clientSecret = new URLSearchParams(window.location.search).get('payment_intent_client_secret'); if (clientSecret) { const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret); if (paymentIntent.status === 'succeeded') { // 执行自定义成功逻辑 onSuccess(); alert('支付成功'); } } }; verifyPayment(); }, [onSuccess]); return <div>支付成功处理中...</div>; }; export default PaymentSuccess; - 修改CheckoutForm中的
confirmPayment配置:const { error, paymentIntent } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}/payment-success`, // 指向成功页面 }, redirect: "if_required", }); // 仅处理无需跳转的支付方式(如信用卡)的成功逻辑 if (paymentIntent?.status === "succeeded") { props.onSuccess(); alert('支付成功'); setIsProcessing(false); }
内容的提问来源于stack exchange,提问作者Vladislav Marinov
相关产品推荐
相关产品推荐

