Stripe支付表单在使用@stripe/react-stripe-js时不断重新渲染。

huangapple go评论105阅读模式
英文:

Stripe payment form constantly re-renders using @stripe/react-stripe-js

问题

我在设置Stripe付款时遇到了一些问题,在我的Medusa-JS Next JS前端中。

checkoutForm中输入卡片详情时,整个StripePayment在单击表单中不是PaymentElement内的任何内容时都会重新渲染。

我怀疑这是由于我的Stripe组件的useEffect中的更改导致的,但我不明白为什么它应该在我的表单内更新。

我的代码

StripePayment - 组件

  1. "use client";
  2. import { useState, useEffect } from "react";
  3. import { loadStripe } from "@stripe/stripe-js";
  4. import { Elements } from "@stripe/react-stripe-js";
  5. import CheckoutForm from "./form";
  6. const STRIPE_PK_KEY = process.env.NEXT_PUBLIC_STRIPE_PK_KEY;
  7. const StripePayment = ({ cart }) => {
  8. const [stripePromise, setStripePromise] = useState(null);
  9. const [clientSecret, setClientSecret] = useState();
  10. useEffect(() => {
  11. if (!STRIPE_PK_KEY && !cart && !cart.payment_sessions) {
  12. return null;
  13. }
  14. const isStripeAvailable = cart.payment_sessions?.some(
  15. (session) => session.provider_id === "stripe"
  16. );
  17. if (!isStripeAvailable) {
  18. return;
  19. }
  20. setStripePromise(loadStripe(STRIPE_PK_KEY));
  21. setClientSecret(cart.payment_session.data.client_secret);
  22. }, [cart.id]);
  23. return (
  24. <div>
  25. <h1>Stripe付款</h1>
  26. {stripePromise && clientSecret && (
  27. <Elements stripe={stripePromise} options={{ clientSecret }}>
  28. <CheckoutForm clientSecret={clientSecret} cart={cart} />
  29. </Elements>
  30. )}
  31. </div>
  32. );
  33. };
  34. export default StripePayment;

CheckoutForm - 组件

  1. "use client";
  2. import {
  3. useElements,
  4. useStripe,
  5. PaymentElement,
  6. } from "@stripe/react-stripe-js";
  7. import { useState } from "react";
  8. import medusaClient from "../../../lib/medusaClient";
  9. export default function Form({ clientSecret, cart }) {
  10. const stripe = useStripe();
  11. const elements = useElements();
  12. const [message, setMessage] = useState("");
  13. const [isProcessing, setIsProcessing] = useState(false);
  14. const handleSubmit = async (e) => {
  15. e.preventDefault();
  16. if (!stripe || !elements) {
  17. console.log("没有 tripe 或 elements");
  18. return;
  19. }
  20. setIsProcessing(true);
  21. const { error, paymentIntent } = await stripe.confirmCardPayment(
  22. clientSecret,
  23. {
  24. payment_method: {
  25. card: elements.getElement(PaymentElement),
  26. billing_details: {
  27. name: e.target.name.value,
  28. email: e.target.email.value,
  29. phone: e.target.phone.value,
  30. address: {
  31. city: cart.shipping_address.city,
  32. country: cart.shipping_address.country,
  33. line1: cart.shipping_address.line1,
  34. line2: cart.shipping_address.line2,
  35. postal_code: cart.shipping_address.postal_code,
  36. },
  37. },
  38. },
  39. }
  40. );
  41. if (error) {
  42. setMessage(error.message);
  43. } else if (paymentIntent && paymentIntent.status === "succeeded") {
  44. setMessage("付款成功");
  45. try {
  46. await medusaClient.carts.complete(cart.id);
  47. console.log("订单已完成");
  48. } catch (err) {
  49. console.error("完成订单时出错:", err);
  50. }
  51. } else {
  52. setMessage("发生了意外错误");
  53. }
  54. console.log("消息:", message);
  55. setIsProcessing(false);
  56. };
  57. return (
  58. <form id="payment-form" onSubmit={(e) => handleSubmit(e)}>
  59. {/* 为了可读性,省略输入标签 */}
  60. <PaymentElement id="payment-element" />
  61. <button
  62. className="px-4 py-2 mt-4 text-white bg-blue-500"
  63. disabled={isProcessing || !stripe || !elements}
  64. id="submit"
  65. >
  66. <span id="button-text">
  67. {isProcessing ? "处理中..." : "立即支付"}
  68. </span>
  69. </button>
  70. {/* 显示任何错误或成功消息 */}
  71. {message && <div id="payment-message">{message}</div>}
  72. </form>
  73. );
  74. }
英文:

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

  1. &quot;use client&quot;;
  2. import { useState, useEffect } from &quot;react&quot;;
  3. import { loadStripe } from &quot;@stripe/stripe-js&quot;;
  4. import { Elements } from &quot;@stripe/react-stripe-js&quot;;
  5. import CheckoutForm from &quot;./form&quot;;
  6. const STRIPE_PK_KEY = process.env.NEXT_PUBLIC_STRIPE_PK_KEY;
  7. const StripePayment = ({ cart }) =&gt; {
  8. const [stripePromise, setStripePromise] = useState(null);
  9. const [clientSecret, setClientSecret] = useState();
  10. useEffect(() =&gt; {
  11. if (!STRIPE_PK_KEY &amp;&amp; !cart &amp;&amp; !cart.payment_sessions) {
  12. return null;
  13. }
  14. const isStripeAvailable = cart.payment_sessions?.some(
  15. (session) =&gt; session.provider_id === &quot;stripe&quot;
  16. );
  17. if (!isStripeAvailable) {
  18. return;
  19. }
  20. setStripePromise(loadStripe(STRIPE_PK_KEY));
  21. setClientSecret(cart.payment_session.data.client_secret);
  22. }, [cart.id]);
  23. return (
  24. &lt;div&gt;
  25. &lt;h1&gt;Stripe payment&lt;/h1&gt;
  26. {stripePromise &amp;&amp; clientSecret &amp;&amp; (
  27. &lt;Elements stripe={stripePromise} options={{ clientSecret }}&gt;
  28. &lt;CheckoutForm clientSecret={clientSecret} cart={cart} /&gt;
  29. &lt;/Elements&gt;
  30. )}
  31. &lt;/div&gt;
  32. );
  33. };
  34. export default StripePayment;

CheckoutForm - component

  1. &quot;use client&quot;;
  2. import {
  3. useElements,
  4. useStripe,
  5. PaymentElement,
  6. } from &quot;@stripe/react-stripe-js&quot;;
  7. import { useState } from &quot;react&quot;;
  8. import medusaClient from &quot;../../../lib/medusaClient&quot;;
  9. export default function Form({ clientSecret, cart }) {
  10. const stripe = useStripe();
  11. const elements = useElements();
  12. const [message, setMessage] = useState(&quot;&quot;);
  13. const [isProcessing, setIsProcessing] = useState(false);
  14. const handleSubmit = async (e) =&gt; {
  15. e.preventDefault();
  16. if (!stripe || !elements) {
  17. console.log(&quot;No tripe or elements&quot;);
  18. return;
  19. }
  20. setIsProcessing(true);
  21. const { error, paymentIntent } = await stripe.confirmCardPayment(
  22. clientSecret,
  23. {
  24. payment_method: {
  25. card: elements.getElement(PaymentElement),
  26. billing_details: {
  27. name: e.target.name.value,
  28. email: e.target.email.value,
  29. phone: e.target.phone.value,
  30. address: {
  31. city: cart.shipping_address.city,
  32. country: cart.shipping_address.country,
  33. line1: cart.shipping_address.line1,
  34. line2: cart.shipping_address.line2,
  35. postal_code: cart.shipping_address.postal_code,
  36. },
  37. },
  38. },
  39. }
  40. );
  41. if (error) {
  42. setMessage(error.message);
  43. } else if (paymentIntent &amp;&amp; paymentIntent.status === &quot;succeeded&quot;) {
  44. setMessage(&quot;Payment succeeded&quot;);
  45. try {
  46. await medusaClient.carts.complete(cart.id);
  47. console.log(&quot;Order completed&quot;);
  48. } catch (err) {
  49. console.error(&quot;Error completing order:&quot;, err);
  50. }
  51. } else {
  52. setMessage(&quot;An unexpected error occurred&quot;);
  53. }
  54. console.log(&quot;Message: &quot;, message);
  55. setIsProcessing(false);
  56. };
  57. return (
  58. &lt;form id=&quot;payment-form&quot; onSubmit={(e) =&gt; handleSubmit(e)}&gt;
  59. {/* INPUT LABELS OMITTED FOR SAKE OF READIBILITY */}
  60. &lt;PaymentElement id=&quot;payment-element&quot; /&gt;
  61. &lt;button
  62. className=&quot;px-4 py-2 mt-4 text-white bg-blue-500&quot;
  63. disabled={isProcessing || !stripe || !elements}
  64. id=&quot;submit&quot;
  65. &gt;
  66. &lt;span id=&quot;button-text&quot;&gt;
  67. {isProcessing ? &quot;Processing ... &quot; : &quot;Pay now&quot;}
  68. &lt;/span&gt;
  69. &lt;/button&gt;
  70. {/* Show any error or success messages */}
  71. {message &amp;&amp; &lt;div id=&quot;payment-message&quot;&gt;{message}&lt;/div&gt;}
  72. &lt;/form&gt;
  73. );
  74. }

答案1

得分: 0

在你的代码中,我看到了一些不同寻常的地方:

  • 你在使用 confirmCardPayment,该方法用于当你使用 CardElement 时;如果你使用 <PaymentElement>必须 使用 confirmPayment
  • 相关的是,我不理解为什么你将 clientSecret 传递给 CheckoutForm 组件;因为你不需要它 —— 你可以使用 useElements(),它提供了已经初始化并知道 clientSecret 的全局 Stripe 实例。也许这多余的依赖导致了你看到的一些重新渲染问题?

正常的集成更像是这样的:

  1. Elements stripe={stripePromise} options={{ clientSecret }}>
  2. <CheckoutForm cart={cart} />
  3. </Elements>

CheckoutForm:

  1. ...
  2. const { error } = await stripe.confirmPayment({
  3. elements, // 来自 useElements;你不需要传递 clientSecret
  4. confirmParams: {
  5. // 确保将此更改为您的支付完成页面
  6. return_url: `${window.location.origin}/completion`,
  7. payment_method_data: {
  8. billing_details: {
  9. name: e.target.name.value,
  10. email: e.target.email.value,
  11. // ...
  12. }
  13. }
  14. },
  15. });
  16. ...

https://github.com/stripe-samples/accept-a-payment/tree/504ffe70e4ac54a21c2c8ce3b5c426c12df6f351/payment-element/client/react-cra/src

https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements&client=react#web-collect-payment-details

我建议尝试重写你的代码以匹配 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 &lt;PaymentElement&gt; you must use confirmPayment 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:

  1. Elements stripe={stripePromise} options={{ clientSecret }}&gt;
  2. &lt;CheckoutForm cart={cart} /&gt;
  3. &lt;/Elements&gt;

CheckoutForm:

  1. ...
  2. const { error } = await stripe.confirmPayment({
  3. elements, // from useElements ; you do not need to pass the clientSecret
  4. confirmParams: {
  5. // Make sure to change this to your payment completion page
  6. return_url: `${window.location.origin}/completion`,
  7. payment_method_data: {
  8. billing_details:{
  9. name: e.target.name.value,
  10. email: e.target.email.value,
  11. ...
  12. }
  13. }
  14. },
  15. });
  16. ...

https://github.com/stripe-samples/accept-a-payment/tree/504ffe70e4ac54a21c2c8ce3b5c426c12df6f351/payment-element/client/react-cra/src

https://stripe.com/docs/payments/accept-a-payment?platform=web&amp;ui=elements&amp;client=react#web-collect-payment-details

I would try to rewrite your code to match Stripe's docs and see if that has an impact.

huangapple
  • 本文由 发表于 2023年3月31日 19:44:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75898162.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定