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来确保服务器能可靠获取支付状态(前端返回的状态可能因网络或篡改不可信),具体实现步骤:
配置Stripe Webhook端点
在Stripe后台的Webhook设置中,添加你的服务器接收地址(比如你代码中的/public/stripe/webhook),并勾选需要监听的事件:payment_intent.succeeded:支付成功时触发payment_intent.payment_failed:支付失败时触发
服务器端验证并处理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
相关产品推荐
相关产品推荐

