medusajs / nextjs-starter-medusa

A performant frontend ecommerce starter template with Next.js 14 and Medusa.
https://next.medusajs.com/
MIT License
1.7k stars 467 forks source link

Clicking on checkout btn on cart page -> Error: Could not find Elements context; You need to wrap the part of your app that mounts <CardElement> in an <Elements> provider. #247

Closed leonwangg1 closed 8 months ago

leonwangg1 commented 8 months ago

Browser: Application error: a client-side exception has occurred (see the browser console for more information).

Plugin used: https://medusajs.com/plugins/medusa-payment-stripe/ Log console:

Error: Could not find Elements context; You need to wrap the part of your app that mounts <CardElement> in an <Elements> provider.
    at x (7296-8fad8bd6261c0c6e.js:1:18695)
    at A (7296-8fad8bd6261c0c6e.js:1:20386)
    at P.o (7296-8fad8bd6261c0c6e.js:1:21352)
    at rb (fd9d1056-4c9fcf9b676e03d0.js:1:40331)
    at iU (fd9d1056-4c9fcf9b676e03d0.js:1:116118)
    at o2 (fd9d1056-4c9fcf9b676e03d0.js:1:94371)
    at fd9d1056-4c9fcf9b676e03d0.js:1:94193
    at o1 (fd9d1056-4c9fcf9b676e03d0.js:1:94200)
    at oV (fd9d1056-4c9fcf9b676e03d0.js:1:91687)
    at oB (fd9d1056-4c9fcf9b676e03d0.js:1:91112)

I get the above error when I want to checkout from cart. However, it seems to not show the error when running on localhost and only on the live server.

VariableVic commented 8 months ago

Hey @leonwangg1,

Let's see if we can figure this out.

Edit: the latter seems unlikely as you mention that it's working on your local server. Sounds like your Stripe session isn't properly initializing on the live server.

leonwangg1 commented 8 months ago

Oddly, when i refresh the page when i get the error, I was able to get into the checkout page.

leonwangg1 commented 8 months ago

It looks like the paymentSession and isStripe values are empty image

And when I reload the page they have values image

vladuma commented 8 months ago

I am facing the same issue. Checkout throws the same error on first load. After a few refreshes - loads correctly. With this error in the server logs:

....
data: {
      code: 'invalid_request_error',
      type: 'duplicate_error',
      message: 'Payment_session with cart_id, provider_id cart_xxx, stripe already exists.'
   }
....

Also it says paymentSession on line 20 of (payment-wrapper/index.tsx) is undefined

Probably this is some nonsense related to next.js rendering "use client" files on the server... So I butchered it and used useEffect to set provider id and it seems to have fixe the issue..

const Wrapper: React.FC<WrapperProps> = ({ cart, children }) => {
  const [providerId, setProviderId] = useState<string | null>(null)
  const isStripe = providerId?.includes("stripe")

  useEffect(() => {
    const paymentSession = cart.payment_session as PaymentSession

    setProviderId(paymentSession?.provider_id)
  }, [])

  if (!providerId) return null

  if (isStripe && providerId && stripePromise) {
    return (
      <StripeWrapper
        paymentSession={cart.payment_session!}
        stripeKey={stripeKey}
        stripePromise={stripePromise}
      >
        {children}
      </StripeWrapper>
    )
  }

  if (providerId === "paypal" && paypalClientId !== undefined && cart) {
    return (
      <PayPalScriptProvider
        options={{
          "client-id": "test",
          currency: cart?.region.currency_code.toUpperCase(),
          intent: "authorize",
          components: "buttons",
        }}
      >
        {children}
      </PayPalScriptProvider>
    )
  }

  return <div>{children}</div>
}

But it would great for some one to come up with a legit solution.

kopec commented 8 months ago

"use client" files on the server... So I butchered it and used useEffect to set provider id and it seems to have fixe the issue..

This unfortunately hasn't fixed it for me as on the first load the providerId is null and doesn't update. Only updates after refreshing the page.

VariableVic commented 8 months ago

This is happening when you enter the checkout from the cart page right? Is Stripe the only enabled payment provider when this issue occurs?

Because if there's multiple payment providers enabled, cart.payment_session should be null until a provider is selected in the checkout flow.

If there's only one provider enabled, Medusa will select it by default, resulting in cart.payment_session being populated when you enter the checkout - even though Stripe context might not be fully loaded yet. I suspect this is causing the issue.

kopec commented 8 months ago

That's right. This is exactly how it happens. Stripe is the only enabled provider and I enter the checkout from the cart page. @VariableVic could you point me to the method does the default selection and how can I listen to the Stripe context load event?

kopec commented 8 months ago

If there's only one provider enabled, Medusa will select it by default, resulting in cart.payment_session being populated when you enter the checkout - even though Stripe context might not be fully loaded yet. I suspect this is causing the issue.

Confirmed. After adding a second payment provider, the issue disappears.

VincentDevp commented 8 months ago

I'm annoyed by this issue awkwardly while there is only one payment method (stripe) in my portal. Is there any sophisticated way to solve it elegantly at the side of storefront? There is a similar issue at #218, but the corresponding codes were marked as outdated. In fact, the issue seems to happen again in the latest version.

cmario92 commented 8 months ago

what if the store only provides one payment provider? is there an WA for that?

VariableVic commented 8 months ago

Yeah I understand this is super annoying. I'm working on a fix. Keep you posted!

VariableVic commented 8 months ago

Guys, I've opened a PR that should solve this issue.

Added in a StripeContext provider that returns true when the Elements context is ready. You can hook into this context to conditionally render Stripe elements in your checkout form.

This way, no Stripe element component should ever render before the Elements context is ready.

Tested and confirmed working locally. Could anyone give this a spin and confirm if it solves the issue?

VincentDevp commented 8 months ago

@VariableVic , I did tests, and the issue was solved. Thanks for your reinforcing!

VariableVic commented 8 months ago

@VincentDevp Thanks for confirming!

I'm gonna merge the fix and close this issue. LMK if anyone is still experiencing difficulties with Stripe after this.

kopec commented 8 months ago

Thanks @VariableVic. This has solved it for me.

bqst commented 8 months ago

FIY, with the following versions :

    "@stripe/react-stripe-js": "^2.5.0",
    "@stripe/stripe-js": "^3.0.4",

I still had the same issue despite the @VariableVic fix.

Please make sure the Element you are attempting to use is still mounted.

To fix it, I had to simply remove the review step and implement the PaymentButton directly at the Payment stage so that the Stripe Element would still be mounted. Btw, I use CardElement.

VariableVic commented 8 months ago

@bqst I tried with the versions you mention, but I can't replicate this issue. I'm able to finish a test order using Stripe just fine. The component containing <CardElement> shouldn't unmount, but get a hidden class when it's not active.

Did you make any changes to the render conditions in the src/modules/checkout/components/payment/index.tsx component?

bqst commented 8 months ago

You're right, I was conditionally rendering like :

{isOpen ? ( <CardElement /> ) : (<>{paymentInfoMap[cart?.payment_session?.provider_id || '']?.title}</>)

That's where the problem was coming from, my bad ! Thank you for the quick answer !