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

React TSX集成Stripe支付:弹出支付表单及服务端通知方案

一、实现React支付表单的显示与支付流程

你当前代码存在几个关键问题:useStripe和useElements必须在<Elements>上下文内部的组件中使用;没有渲染支付表单(CardElement或CheckoutForm);点击按钮后直接尝试确认支付却没有让用户输入卡片信息的入口。以下是修正后的实现方案:

1. 调整StripeHandler组件逻辑

通过状态控制支付表单的显示,获取clientSecret后触发表单渲染:

import React, { useState } from "react";
import axios from "axios";
import { loadStripe } from "@stripe/stripe-js";
import Config from "./Config.jsx";
import ConfigDev from "./ConfigDev.jsx";
import { restApiPostSimple } from "./RestManager.js";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm.jsx";

const StripeHandler = () => {
  const [amount, setAmount] = useState<number | undefined>(undefined);
  const [showCheckout, setShowCheckout] = useState(false);
  const [clientSecret, setClientSecret] = useState<string | null>(null);
  
  const isDevelopment = process.env.NODE_ENV === "development";
  const stripeKey = isDevelopment ? ConfigDev.stripeKey : Config.stripeKey;
  const stripePromise = loadStripe(stripeKey);
  const createIntentIdPath = "/mobile-payment/create-payment-intent?amount=";

  const handlePayment = async (amountToCharge: number) => {
    try {
      const response = await restApiPostSimple(
        createIntentIdPath + amountToCharge
      ); 
      setAmount(amountToCharge);
      setClientSecret(response.data.clientSecret);
      setShowCheckout(true); // 获取clientSecret后显示支付表单
    } catch (error) {
      console.error("Error:", error);
    }
  };

  return (
    <div>
      <Elements stripe={stripePromise}>
        <div>
          {/* 金额选择按钮 */}
          <button onClick={() => handlePayment(1)}>1$</button>
          <br /><br />
          <button onClick={() => handlePayment(5)}>5$</button>
          <br /><br />
          <button onClick={() => handlePayment(10)}>10$</button>
          <br /><br />

          {/* 支付表单(选中金额后显示) */}
          {showCheckout && clientSecret && (
            <CheckoutForm 
              clientSecret={clientSecret} 
              amount={amount}
              onClose={() => {
                setShowCheckout(false);
                setClientSecret(null);
              }}
            />
          )}
        </div>
      </Elements>
    </div>
  );
};

export default StripeHandler;

2. 完善CheckoutForm组件

在表单内部处理卡片输入与支付确认逻辑(useStripe和useElements必须在这里使用):

// CheckoutForm.jsx
import { useState } from "react";
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";

const CheckoutForm = ({ clientSecret, amount, onClose }) => {
  const stripe = useStripe();
  const elements = useElements();
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    setLoading(true);
    setError(null);

    const cardElement = elements.getElement(CardElement);
    if (!cardElement) return;

    // 确认卡片支付
    const result = await stripe.confirmCardPayment(clientSecret, {
      payment_method: {
        card: cardElement,
        billing_details: {
          name: "请输入持卡人姓名", // 可改为用户输入框
        },
      },
    });

    if (result.error) {
      setError(result.error.message);
    } else {
      if (result.paymentIntent.status === "succeeded") {
        alert(`支付成功!金额:${amount}$`);
        onClose();
      }
    }
    setLoading(false);
  };

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: "400px", margin: "0 auto" }}>
      <div>
        <h4>支付金额:{amount}$</h4>
        <CardElement
          options={{
            style: {
              base: { fontSize: "16px", padding: "12px" },
            },
          }}
        />
      </div>
      {error && <p style={{ color: "red", marginTop: "10px" }}>{error}</p>}
      <button
        type="submit"
        disabled={loading || !stripe}
        style={{
          marginTop: "20px",
          padding: "10px 20px",
          backgroundColor: "#0070f3",
          color: "white",
          border: "none",
          borderRadius: "4px",
          cursor: "pointer",
        }}
      >
        {loading ? "处理中..." : "确认支付"}
      </button>
      <button
        type="button"
        onClick={onClose}
        style={{
          marginTop: "10px",
          padding: "8px 16px",
          backgroundColor: "#eee",
          border: "none",
          borderRadius: "4px",
          cursor: "pointer",
        }}
      >
        取消
      </button>
    </form>
  );
};

export default CheckoutForm;

二、服务器端获取支付状态的方案

客户端支付完成后,必须依赖Stripe Webhook来确保服务器能可靠获取支付状态(前端返回的状态可能因网络或篡改不可信),具体实现步骤:

  1. 配置Stripe Webhook端点
    在Stripe后台的Webhook设置中,添加你的服务器接收地址(比如你代码中的/public/stripe/webhook),并勾选需要监听的事件:

    • payment_intent.succeeded:支付成功时触发
    • payment_intent.payment_failed:支付失败时触发
  2. 服务器端验证并处理Webhook事件
    以Node.js为例,服务器需要验证Stripe的签名,确保请求来自官方,然后根据事件类型处理业务逻辑:

const stripe = require('stripe')('你的Stripe秘钥');
const express = require('express');
const app = express();

// 解析Stripe的原始请求体(不能用json解析,否则签名验证失败)
app.post('/public/stripe/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const webhookSecret = '你的Webhook签名秘钥(从Stripe后台获取)';

  let event;
  try {
    // 验证请求签名
    event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
  } catch (err) {
    return res.status(400).send(`Webhook验证失败:${err.message}`);
  }

  // 根据事件类型处理业务
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      // 这里添加你的业务逻辑:比如更新订单状态、发送支付成功通知等
      console.log(`支付成功,Payment Intent ID:${paymentIntent.id}`);
      break;
    case 'payment_intent.payment_failed':
      const failedIntent = event.data.object;
      // 处理支付失败逻辑:比如记录失败原因、通知用户
      console.log(`支付失败,原因:${failedIntent.last_payment_error?.message}`);
      break;
    default:
      console.log(`未处理的事件类型:${event.type}`);
  }

  res.json({ received: true });
});

内容的提问来源于stack exchange,提问作者Hard Worker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 07:40:06