英文:
Stripe payment form constantly re-renders using @stripe/react-stripe-js
问题
我在设置Stripe付款时遇到了一些问题,在我的Medusa-JS Next JS前端中。
在checkoutForm
中输入卡片详情时,整个StripePayment
在单击表单中不是PaymentElement
内的任何内容时都会重新渲染。
我怀疑这是由于我的Stripe组件的useEffect中的更改导致的,但我不明白为什么它应该在我的表单内更新。
我的代码
StripePayment - 组件
"use client";
import { useState, useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "./form";
const STRIPE_PK_KEY = process.env.NEXT_PUBLIC_STRIPE_PK_KEY;
const StripePayment = ({ cart }) => {
const [stripePromise, setStripePromise] = useState(null);
const [clientSecret, setClientSecret] = useState();
useEffect(() => {
if (!STRIPE_PK_KEY && !cart && !cart.payment_sessions) {
return null;
}
const isStripeAvailable = cart.payment_sessions?.some(
(session) => session.provider_id === "stripe"
);
if (!isStripeAvailable) {
return;
}
setStripePromise(loadStripe(STRIPE_PK_KEY));
setClientSecret(cart.payment_session.data.client_secret);
}, [cart.id]);
return (
<div>
<h1>Stripe付款</h1>
{stripePromise && clientSecret && (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm clientSecret={clientSecret} cart={cart} />
</Elements>
)}
</div>
);
};
export default StripePayment;
CheckoutForm - 组件
"use client";
import {
useElements,
useStripe,
PaymentElement,
} from "@stripe/react-stripe-js";
import { useState } from "react";
import medusaClient from "../../../lib/medusaClient";
export default function Form({ clientSecret, cart }) {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
console.log("没有 tripe 或 elements");
return;
}
setIsProcessing(true);
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: elements.getElement(PaymentElement),
billing_details: {
name: e.target.name.value,
email: e.target.email.value,
phone: e.target.phone.value,
address: {
city: cart.shipping_address.city,
country: cart.shipping_address.country,
line1: cart.shipping_address.line1,
line2: cart.shipping_address.line2,
postal_code: cart.shipping_address.postal_code,
},
},
},
}
);
if (error) {
setMessage(error.message);
} else if (paymentIntent && paymentIntent.status === "succeeded") {
setMessage("付款成功");
try {
await medusaClient.carts.complete(cart.id);
console.log("订单已完成");
} catch (err) {
console.error("完成订单时出错:", err);
}
} else {
setMessage("发生了意外错误");
}
console.log("消息:", message);
setIsProcessing(false);
};
return (
<form id="payment-form" onSubmit={(e) => handleSubmit(e)}>
{/* 为了可读性,省略输入标签 */}
<PaymentElement id="payment-element" />
<button
className="px-4 py-2 mt-4 text-white bg-blue-500"
disabled={isProcessing || !stripe || !elements}
id="submit"
>
<span id="button-text">
{isProcessing ? "处理中..." : "立即支付"}
</span>
</button>
{/* 显示任何错误或成功消息 */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
英文:
I have some issues setting up Stripe payment in my Medusa-JS Next JS frontend.
When entering card details in the checkoutForm
the entire StripePayment
re-renders all the time when clicking on anything inside my form that is not the PaymentElement
.
I have a suspicion that it is due to changes in my Stripe component’s useEffect but I cannot see why it should be updated inside my form.
My code
StripePayment - component
"use client";
import { useState, useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "./form";
const STRIPE_PK_KEY = process.env.NEXT_PUBLIC_STRIPE_PK_KEY;
const StripePayment = ({ cart }) => {
const [stripePromise, setStripePromise] = useState(null);
const [clientSecret, setClientSecret] = useState();
useEffect(() => {
if (!STRIPE_PK_KEY && !cart && !cart.payment_sessions) {
return null;
}
const isStripeAvailable = cart.payment_sessions?.some(
(session) => session.provider_id === "stripe"
);
if (!isStripeAvailable) {
return;
}
setStripePromise(loadStripe(STRIPE_PK_KEY));
setClientSecret(cart.payment_session.data.client_secret);
}, [cart.id]);
return (
<div>
<h1>Stripe payment</h1>
{stripePromise && clientSecret && (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm clientSecret={clientSecret} cart={cart} />
</Elements>
)}
</div>
);
};
export default StripePayment;
CheckoutForm - component
"use client";
import {
useElements,
useStripe,
PaymentElement,
} from "@stripe/react-stripe-js";
import { useState } from "react";
import medusaClient from "../../../lib/medusaClient";
export default function Form({ clientSecret, cart }) {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
console.log("No tripe or elements");
return;
}
setIsProcessing(true);
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: elements.getElement(PaymentElement),
billing_details: {
name: e.target.name.value,
email: e.target.email.value,
phone: e.target.phone.value,
address: {
city: cart.shipping_address.city,
country: cart.shipping_address.country,
line1: cart.shipping_address.line1,
line2: cart.shipping_address.line2,
postal_code: cart.shipping_address.postal_code,
},
},
},
}
);
if (error) {
setMessage(error.message);
} else if (paymentIntent && paymentIntent.status === "succeeded") {
setMessage("Payment succeeded");
try {
await medusaClient.carts.complete(cart.id);
console.log("Order completed");
} catch (err) {
console.error("Error completing order:", err);
}
} else {
setMessage("An unexpected error occurred");
}
console.log("Message: ", message);
setIsProcessing(false);
};
return (
<form id="payment-form" onSubmit={(e) => handleSubmit(e)}>
{/* INPUT LABELS OMITTED FOR SAKE OF READIBILITY */}
<PaymentElement id="payment-element" />
<button
className="px-4 py-2 mt-4 text-white bg-blue-500"
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>
);
}
答案1
得分: 0
在你的代码中,我看到了一些不同寻常的地方:
- 你在使用
confirmCardPayment
,该方法用于当你使用 CardElement 时;如果你使用<PaymentElement>
,必须 使用confirmPayment
。 - 相关的是,我不理解为什么你将 clientSecret 传递给 CheckoutForm 组件;因为你不需要它 —— 你可以使用
useElements()
,它提供了已经初始化并知道 clientSecret 的全局 Stripe 实例。也许这多余的依赖导致了你看到的一些重新渲染问题?
正常的集成更像是这样的:
Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm cart={cart} />
</Elements>
CheckoutForm:
...
const { error } = await stripe.confirmPayment({
elements, // 来自 useElements;你不需要传递 clientSecret
confirmParams: {
// 确保将此更改为您的支付完成页面
return_url: `${window.location.origin}/completion`,
payment_method_data: {
billing_details: {
name: e.target.name.value,
email: e.target.email.value,
// ...
}
}
},
});
...
我建议尝试重写你的代码以匹配 Stripe 的文档,看看是否有影响。
英文:
There's some unusual things in your code I see:
- You're using
confirmCardPayment
, which is used for when you are using the CardElement, if you're using the<PaymentElement>
you must useconfirmPayment
instead - Related to that, I don't understand why you pass the clientSecret down to the CheckoutForm component; since you don't need it -— you would just use the
useElements()
which provides the global Stripe instance that's already initialised and know the clientSecret. Maybe this extra dependency causes some of the re-render issue you see?
The normal integration is more like this:
Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm cart={cart} />
</Elements>
CheckoutForm:
...
const { error } = await stripe.confirmPayment({
elements, // from useElements ; you do not need to pass the clientSecret
confirmParams: {
// Make sure to change this to your payment completion page
return_url: `${window.location.origin}/completion`,
payment_method_data: {
billing_details:{
name: e.target.name.value,
email: e.target.email.value,
...
}
}
},
});
...
I would try to rewrite your code to match Stripe's docs and see if that has an impact.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论