如何在React.js中实现Stripe Accordion支付布局?
实现Stripe Accordion支付布局失败的问题
我已完成Stripe Tab支付布局的实现,但无法切换到Accordion布局,不清楚PaymentElement的正确配置方式。以下是我的相关代码和尝试过的修改:
服务端代码
const express = require("express"); const app = express(); const { resolve } = require("path"); // Replace if using a different env file or config const env = require("dotenv").config({ path: "./.env" }); const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY, { apiVersion: "2022-08-01", }); app.use(express.static(process.env.STATIC_DIR)); app.get("/", (req, res) => { const path = resolve(process.env.STATIC_DIR + "/index.html"); res.sendFile(path); }); app.get("/config", (req, res) => { res.send({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY, }); }); app.post("/create-payment-intent", async (req, res) => { try { const paymentIntent = await stripe.paymentIntents.create({ currency: "EUR", amount: 1999, automatic_payment_methods: { enabled: true }, }); // Send publishable key and PaymentIntent details to client res.send({ clientSecret: paymentIntent.client_secret, }); } catch (e) { return res.status(400).send({ error: { message: e.message, }, }); } }); app.listen(5252, () => console.log(`Node server listening at http://localhost:5252`) );
客户端Payment组件代码
import { useEffect, useState } from "react"; import { Elements } from "@stripe/react-stripe-js"; import CheckoutForm from "./CheckoutForm"; import { loadStripe } from "@stripe/stripe-js"; function Payment() { const [stripePromise, setStripePromise] = useState(null); const [clientSecret, setClientSecret] = useState(""); useEffect(() => { fetch("/config").then(async (r) => { const { publishableKey } = await r.json(); setStripePromise(loadStripe(publishableKey)); }); }, []); useEffect(() => { fetch("/create-payment-intent", { method: "POST", body: JSON.stringify({}), }).then(async (result) => { var { clientSecret } = await result.json(); setClientSecret(clientSecret); }); }, []); return ( <> <h1>React Stripe and the Payment Element</h1> {clientSecret && stripePromise && ( <Elements stripe={stripePromise} options={{ clientSecret }}> <CheckoutForm /> </Elements> )} </> ); } export default Payment;
原CheckoutForm代码
import { PaymentElement } from "@stripe/react-stripe-js"; import { useState } from "react"; import { useStripe, useElements } from "@stripe/react-stripe-js"; export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const [message, setMessage] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); if (!stripe || !elements) { // Stripe.js has not yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } setIsProcessing(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: `${window.location.origin}/completion`, }, }); if (error.type === "card_error" || error.type === "validation_error") { setMessage(error.message); } else { setMessage("An unexpected error occured."); } setIsProcessing(false); }; const paymentElement = elements.create('payment', { layout: { type: 'accordion', defaultCollapsed: false, radios: true, spacedAccordionItems: false } }); return ( <form id="payment-form" onSubmit={handleSubmit}> <PaymentElement id="payment-element"/> <button disabled={isProcessing || !stripe || !elements} id="submit"> <span id="button-text"> {isProcessing ? "Processing ... " : "Pay now"} </span> </button> {/* Show any error or success messages */} {message && <div id="payment-message">{message}</div>} </form> ); }
尝试过的修改(未生效)
const paymentElement = elements.create('payment', { layout: { type: 'accordion', defaultCollapsed: false, radios: true, spacedAccordionItems: false } }); return ( <form id="payment-form" onSubmit={handleSubmit}> <PaymentElement id="payment-element" options={{paymentElement }}/> <button disabled={isProcessing || !stripe || !elements} id="submit"> <span id="button-text"> {isProcessing ? "Processing ... " : "Pay now"} </span> </button> {/* Show any error or success messages */} {message && <div id="payment-message">{message}</div>} </form> );
解决方案
问题核心是混用了Stripe原生JS的element创建方式和React组件的配置方式:elements.create()是原生JS的写法,而React版本的PaymentElement组件需要直接通过options属性传入布局配置,不需要手动创建element实例。
正确的CheckoutForm代码如下:
import { PaymentElement } from "@stripe/react-stripe-js"; import { useState } from "react"; import { useStripe, useElements } from "@stripe/react-stripe-js"; export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const [message, setMessage] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); if (!stripe || !elements) { return; } setIsProcessing(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}/completion`, }, }); if (error?.type === "card_error" || error?.type === "validation_error") { setMessage(error.message); } else if (error) { setMessage("An unexpected error occured."); } setIsProcessing(false); }; // 直接定义Accordion布局配置 const paymentElementOptions = { layout: { type: "accordion", defaultCollapsed: false, radios: true, spacedAccordionItems: false } }; return ( <form id="payment-form" onSubmit={handleSubmit}> {/* 将配置通过options属性传入PaymentElement */} <PaymentElement id="payment-element" options={paymentElementOptions} /> <button disabled={isProcessing || !stripe || !elements} id="submit"> <span id="button-text"> {isProcessing ? "Processing ... " : "Pay now"} </span> </button> {message && <div id="payment-message">{message}</div>} </form> ); }
关键修改点:
- 删除
elements.create()的冗余代码,直接定义布局配置对象 - 将配置对象通过
PaymentElement的options属性传入 - 增加
error的空判断,避免未捕获的异常
内容的提问来源于stack exchange,提问作者Shaker Ahamed
相关产品推荐
相关产品推荐

