You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React集成Stripe PaymentElement遇UI及支付回调问题求助

React集成Stripe的问题排查与解决方案

问题描述

  1. PaymentElement UI显示异常:按Stripe官方教程实现后,PaymentElement仅显示简陋界面,无法展示教程中的完整卡片及多支付方式UI。
  2. 支付流程异常:输入信息后跳转到错误页面;测试支付成功后,系统自动跳转至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展示。

修复步骤

  1. 更新前端Stripe依赖:
    npm update @stripe/react-stripe-js @stripe/stripe-js
    
  2. 修改CheckoutForm中的PaymentElement布局配置:
    // 换成accordion布局或直接删除配置用默认样式
    const paymentElementOptions = {
      layout: "accordion",
    };
    
  3. 服务端调整PaymentIntent创建配置,强制在页面内展示所有支付方式:
    automatic_payment_methods: {
      enabled: true,
      allow_redirects: 'never'
    },
    

问题2:跳转错误与回调不执行的修复

原因

  • 域名未加入Stripe后台允许列表,导致跳转时触发错误;
  • 需跳转的支付方式完成后,前端confirmPayment不会返回成功状态,必须在跳转后的页面重新验证支付结果。

修复步骤

  1. 配置Stripe允许域名:
    登录Stripe后台,进入支付设置→域名,添加你的业务域名(包括本地开发的localhost:3000)。
  2. 创建支付成功页面并添加验证逻辑:
    // 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;
    
  3. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:48:08