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

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

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

&quot;use client&quot;;
import { useState, useEffect } from &quot;react&quot;;
import { loadStripe } from &quot;@stripe/stripe-js&quot;;
import { Elements } from &quot;@stripe/react-stripe-js&quot;;
import CheckoutForm from &quot;./form&quot;;

const STRIPE_PK_KEY = process.env.NEXT_PUBLIC_STRIPE_PK_KEY;

const StripePayment = ({ cart }) =&gt; {
  const [stripePromise, setStripePromise] = useState(null);
  const [clientSecret, setClientSecret] = useState();

  useEffect(() =&gt; {
    if (!STRIPE_PK_KEY &amp;&amp; !cart &amp;&amp; !cart.payment_sessions) {
      return null;
    }
    const isStripeAvailable = cart.payment_sessions?.some(
      (session) =&gt; session.provider_id === &quot;stripe&quot;
    );
    if (!isStripeAvailable) {
      return;
    }
    setStripePromise(loadStripe(STRIPE_PK_KEY));
    setClientSecret(cart.payment_session.data.client_secret);
  }, [cart.id]);

  return (
    &lt;div&gt;
      &lt;h1&gt;Stripe payment&lt;/h1&gt;
      {stripePromise &amp;&amp; clientSecret &amp;&amp; (
        &lt;Elements stripe={stripePromise} options={{ clientSecret }}&gt;
          &lt;CheckoutForm clientSecret={clientSecret} cart={cart} /&gt;
        &lt;/Elements&gt;
      )}
    &lt;/div&gt;
  );
};

export default StripePayment;

CheckoutForm - component

&quot;use client&quot;;
import {
  useElements,
  useStripe,
  PaymentElement,
} from &quot;@stripe/react-stripe-js&quot;;
import { useState } from &quot;react&quot;;
import medusaClient from &quot;../../../lib/medusaClient&quot;;

export default function Form({ clientSecret, cart }) {
  const stripe = useStripe();
  const elements = useElements();

  const [message, setMessage] = useState(&quot;&quot;);
  const [isProcessing, setIsProcessing] = useState(false);

  const handleSubmit = async (e) =&gt; {
    e.preventDefault();

    if (!stripe || !elements) {
      console.log(&quot;No tripe or elements&quot;);
      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 &amp;&amp; paymentIntent.status === &quot;succeeded&quot;) {
      setMessage(&quot;Payment succeeded&quot;);
      try {
        await medusaClient.carts.complete(cart.id);
        console.log(&quot;Order completed&quot;);
      } catch (err) {
        console.error(&quot;Error completing order:&quot;, err);
      }
    } else {
      setMessage(&quot;An unexpected error occurred&quot;);
    }
    console.log(&quot;Message: &quot;, message);
    setIsProcessing(false);
  };

  return (
    &lt;form id=&quot;payment-form&quot; onSubmit={(e) =&gt; handleSubmit(e)}&gt;
			{/* INPUT LABELS OMITTED FOR SAKE OF READIBILITY */}
      &lt;PaymentElement id=&quot;payment-element&quot; /&gt;
      &lt;button
        className=&quot;px-4 py-2 mt-4 text-white bg-blue-500&quot;
        disabled={isProcessing || !stripe || !elements}
        id=&quot;submit&quot;
      &gt;
        &lt;span id=&quot;button-text&quot;&gt;
          {isProcessing ? &quot;Processing ... &quot; : &quot;Pay now&quot;}
        &lt;/span&gt;
      &lt;/button&gt;
      {/* Show any error or success messages */}
      {message &amp;&amp; &lt;div id=&quot;payment-message&quot;&gt;{message}&lt;/div&gt;}
    &lt;/form&gt;
  );
}

答案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,
        // ...
      }
    }
  },
});
...

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:

Elements stripe={stripePromise} options={{ clientSecret }}&gt;
&lt;CheckoutForm cart={cart} /&gt;
&lt;/Elements&gt;

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, 
...
}
}
},
});
...

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:

确定