Home > Mobile >  Slow loading time of Stripe Payment Element
Slow loading time of Stripe Payment Element

Time:03-03

I have a checkout page that primarily contains the new Stripe Payment Element and a PayPal payment button (from the Standard Checkout integration). The buttons load fast enough but the credit card form for the Payment Element takes over 5 seconds when loading locally via XAMPP, and I imagine the same would apply to the Google Pay and Apple Pay buttons if they rendered in testing mode. I expect that kind of speed in the development environment to translate to at least 2-3 seconds in production on a fast connection, which is still really slow.

Assuming that kind of loading speed is normal for the Stripe Payment Element, are there any strategies I can use to make it load faster?

In any case, I'd like to at least make the perceived loading speed faster by adding a spinner/preloader that displays until the form has loaded, which would also minimise the jarring content jump that currently occurs when the form loads (to be honest, I'm surprised that Stripe is fine with this UX in the first place). How would I integrate such a spinner into Stripe's client-side code?

Here is the client-side code in question in checkout.js, only slightly adapted from Stripe's Quickstart guide:

const stripe = Stripe(<PK_TEST_KEY>);

const fonts = [
    {
        cssSrc:
            "https://fonts.googleapis.com/css2?family=Open Sans:wght@300;400;500;600;700&display=swap",
    },
];

const appearance = {
    theme: "stripe",
    labels: "floating",
    variables: {
        colorPrimary: "#000",
        colorPrimaryText: "#fff",
        colorText: "#fff",
        fontFamily: "Open Sans, Segoe UI, sans-serif",
        borderRadius: "4px",
        fontSizeBase: "1em",
        fontSizeXs: "0.7em",
        fontWeightNormal: "400",
        fontWeightMedium: "400",
        fontWeightLight: "400",
    },
};
   
let elements;

initialize();
checkStatus();

document
    .querySelector("#payment-form")
    .addEventListener("submit", handleSubmit);

// Fetches a payment intent and captures the client secret
async function initialize() {
    const { clientSecret } = await fetch("/payment/stripe", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "X-CSRF-TOKEN": document.querySelector('input[name="_token"]').value,
        },
    }).then((r) => r.json());

    elements = stripe.elements({ fonts, appearance, clientSecret });

    const paymentElement = elements.create("payment");
    paymentElement.mount("#payment-element");
}

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

    const { error } = await stripe.confirmPayment({
        elements,
        confirmParams: {
            // Make sure to change this to your payment completion page
            return_url: "http://localhost/success",
        },
    });

    // This point will only be reached if there is an immediate error when
    // confirming the payment. Otherwise, your customer will be redirected to
    // your `return_url`. For some payment methods like iDEAL, your customer will
    // be redirected to an intermediate site first to authorize the payment, then
    // redirected to the `return_url`.
    if (error.type === "card_error" || error.type === "validation_error") {
        showMessage(error.message);
    } else {
        showMessage("An unexpected error occured.");
    }

    setLoading(false);
}

// Fetches the payment intent status after payment submission
async function checkStatus() {
    const clientSecret = new URLSearchParams(window.location.search).get(
        "payment_intent_client_secret"
    );

    if (!clientSecret) {
        return;
    }

    const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);

    switch (paymentIntent.status) {
        case "succeeded":
            showMessage("Payment succeeded!");
            break;
        case "processing":
            showMessage("Your payment is processing.");
            break;
        case "requires_payment_method":
            showMessage("Your payment was not successful, please try again.");
            break;
        default:
            showMessage("Something went wrong.");
            break;
    }
}

// ------- UI helpers -------

function showMessage(messageText) {
    const messageContainer = document.querySelector("#payment-message");

    messageContainer.classList.remove("hidden");
    messageContainer.textContent = messageText;

    setTimeout(function () {
        messageContainer.classList.add("hidden");
        messageText.textContent = "";
    }, 10000);
}

// Show a spinner on payment submission
function setLoading(isLoading) {
    if (isLoading) {
        // Disable the button and show a spinner
        document.querySelector("#submit").disabled = true;
        document.querySelector("#spinner").classList.remove("hidden");
        document.querySelector("#button-text").classList.add("hidden");
    } else {
        document.querySelector("#submit").disabled = false;
        document.querySelector("#spinner").classList.add("hidden");
        document.querySelector("#button-text").classList.remove("hidden");
    }
}

CodePudding user response:

The main way to implement a loading spinner would be to start animating one at the start of the initialize() function, and then listen to the "ready" event of the PaymentElement for when to hide it.

Stripe's sample code has a spinner for the submit button but that unfortunately doesn't really work when applied to other DOM elements, so you'd have to roll your own spinner rather than re-using it directly. But that's the idea!

https://stripe.com/docs/js/element/events/on_ready

async function initialize() {
  loaderOn(); // start showing a spinner and maybe apply `hidden` to the payment-element div
  const response = await fetch("/create-payment-intent", {
  ...
  ...

  const paymentElement = elements.create("payment");
  paymentElement.on("ready", function(){
    loaderOff(); // hide the spinner and maybe remove `hidden` from the payment-element div
  })
  paymentElement.mount("#payment-element"); 
  • Related