Search code examples
javascriptlaravelasynchronousstripe-paymentses6-promise

Prevent Stripe confirmPayment from redirecting when custom input field is invalid


I have custom fields for name and email on the same checkout page as Stripe's Payment Element. I've linked these fields to the form for the Payment Element using the form attribute:

<input type="text" form="payment-form" id="name" ... required>
<input type="email" form="payment-form" id="email" ... required>

I set novalidate on the form to disable default browser validation and enable Bootstrap's own validation:

        <div class="stripe-container">
        <form id="payment-form" novalidate>
        {{csrf_field()}}
        <div id="payment-element">
        <!-- Stripe.js injects the Payment Element here-->
        </div>
        <div class="loading-spinner"></div>
        <button id="submit">
        <div class="spinner hidden" id="spinner"></div>
        <span id="button-text">Pay now</span>
        </button>
        <div id="payment-message" class="hidden"></div>
        </form>
        </div>

And modified the Payment Element's JS to trigger the validation when "Pay now" is clicked:

async function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);

    // Added JS to trigger Bootstrap validation for custom fields
    paymentInputs = document.querySelectorAll(".payment-input");
    Array.prototype.slice.call(paymentInputs).forEach(function (paymentInput) {
        paymentInput.classList.remove("is-invalid");
        if (!paymentInput.checkValidity()) {
            paymentInput.classList.add("is-invalid");
            setLoading(false);
        }
    });

    const { error } = await stripe.confirmPayment({
        elements,
        confirmParams: {
            return_url: process.env.MIX_APP_URL + "/success",
        },
    });

    ...

All this works perfectly to show validation errors for my custom fields, and even prevent the submit when one of the Stripe fields fails validation. The problem is when all of the Stripe fields are valid, clicking the payment button triggers the failed validation for my custom fields as expected, but a few seconds later simply redirects to the return_url, likely because of confirmPayment resolving successfully.

How do I prevent confirmPayment from resolving when my custom fields fail validation? I'm sure this has something to do with how asynchronous functions and Promises work, but I'm not familiar enough with them to know what.


Solution

  • You can avoid calling stripe.confirmPayment if your additional input fields are invalid.

    let isInvalid = false;
    Array.prototype.slice.call(paymentInputs).forEach(function (paymentInput) {
        paymentInput.classList.remove("is-invalid");
        if (!paymentInput.checkValidity()) {
            paymentInput.classList.add("is-invalid");
            setLoading(false);
            isInvalid = true;
        }
    });
    
    if (isInvalid) {
      return;
    } 
    
    // Only gets here if other inputs are valid
    const { error } = await stripe.confirmPayment({
        elements,
        confirmParams: {
            return_url: process.env.MIX_APP_URL + "/success",
        },
    });