Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element

Viewed 981

I'm trying to integrate a Stripe payments page using the React "Elements". I'm following the tutorial from https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements#web-submit-payment and I've gotten to step 5, "Submit the payment to Stripe". My code doesn't look much different from the example, but whenever I try to submit a payment this error:

Invalid value for stripe.confirmPayment(): elements should have a mounted Payment Element.

Thrown from stripe.confirmPayment. I've included the <Elements/> and <PaymentElement/> on the page, and passed the return value from useElements() so I'm really not sure what I'm missing.


Here's my checkout form:

function StripeCheckoutForm({paymentIntent,booking}: StripeCheckoutFormProps) {
    const stripe = useStripe();
    const elements = useElements();
    const [confirming,setConfirming] = useState(false)

    const handleSubmit = async (ev: FormEvent<HTMLFormElement>) => {
        ev.preventDefault();

        if (!stripe || !elements) {
            notifyWarning("Still loading. Please wait a few seconds and then try again.");
            return;
        }

        setConfirming(true)
        try {
            const {error} = await stripe.confirmPayment({
                //`Elements` instance that was used to create the Payment Element
                elements,
                confirmParams: {
                    return_url: resolveRoute('customerPaymentReceived',{key:booking.key}),
                },
            })
            if(error) {
                notifyError(error.message)
                setConfirming(false)
            }
        } catch(error) {
            setConfirming(false)
            if(error?.message) {
                notifyError(error.message)  // <-- error shown here
            } else {
                notifyError("Something went wrong");
            }
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <PaymentElement/>
            <BlockSpacer height="1rem"/>
            <ActionButton disabled={!stripe || !elements || confirming} type="submit" className="btn-phone btn-fullwidth">Pay {paymentIntent.amountFormatted}</ActionButton>
        </form>
    )
}

And that form is inside this component, similar to the example shown in step 4:

import {Elements as StripeElements} from '@stripe/react-stripe-js';
import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js';

function StripePaymentForm({stripeAccountId,paymentIntent,booking}: StripePaymentFormProps) {
    const options = {
        clientSecret: paymentIntent.clientSecret,
        loader: 'always',
    }
    return (
        <StripeElements stripe={getStripeConnect(stripeAccountId)} options={options}>
            <StripeCheckoutForm paymentIntent={paymentIntent} booking={booking}/>
        </StripeElements>
    )
}

The only thing I can see that's different is that I'm using a Connect account, so I'm passing in account ID in when I load Stripe. getStripeConnect is basically

loadStripe(STRIPE_PUBLIC_KEY, {stripeAccount: CONNECTED_ACCOUNT_ID})

You can see the React component tree here if it helps:

enter image description here

What am I missing?


I'm guessing this stems from useElements() is not finding any elements:

enter image description here

But I still don't know why.

2 Answers

I believe your loadStripe() promise isn't resolved at the time it is passed to the Elements provider, thus useElements returns null. Either load it earlier or await it and it should resolve your issue.

Related