diff --git a/CRM/Stripe/AJAX.php b/CRM/Stripe/AJAX.php
index 3986f30e1cbf56c18dd9801f5d67bdbef5f8d616..e0c2b5c19efadc1d4c75f4fb657f5cf0af670d4c 100644
--- a/CRM/Stripe/AJAX.php
+++ b/CRM/Stripe/AJAX.php
@@ -9,6 +9,8 @@
  +--------------------------------------------------------------------+
  */
 
+use CRM_Stripe_ExtensionUtil as E;
+
 /**
  * Class CRM_Stripe_AJAX
  */
@@ -154,7 +156,13 @@ class CRM_Stripe_AJAX {
     }
     else {
       // Invalid status
-      CRM_Utils_JSON::output(['error' => ['message' => 'Invalid PaymentIntent status: ' . $intent->status]]);
+      if (isset($intent->last_payment_error->message)) {
+        $message = E::ts('Payment failed: %1', [1 => $intent->last_payment_error->message]);
+      }
+      else {
+        $message = E::ts('Payment failed.');
+      }
+      CRM_Utils_JSON::output(['error' => ['message' => $message]]);
     }
   }
 
diff --git a/js/civicrmStripeConfirm.js b/js/civicrmStripeConfirm.js
index a540f5c0c6d377f97d4f29e2c3a42c3e1ad6e153..a75252aec2e3e0b6b1c37aa639182275185d5002 100644
--- a/js/civicrmStripeConfirm.js
+++ b/js/civicrmStripeConfirm.js
@@ -14,6 +14,10 @@
       if (result.error) {
         // Show error from server on payment form
         CRM.payment.debugging(confirm.scriptName, result.error.message);
+        confirm.swalFire({
+          title: result.error.message,
+          icon: 'error',
+        });
       }
       else
         if (result.requires_action) {
@@ -23,6 +27,10 @@
         else {
           // All good, nothing more to do
           CRM.payment.debugging(confirm.scriptName, 'success - payment captured');
+          confirm.swalFire({
+            title: 'Payment successful',
+            icon: 'success',
+          });
         }
     },
 
@@ -62,6 +70,13 @@
 
     handleCardConfirm: function() {
       CRM.payment.debugging(confirm.scriptName, 'handle card confirm');
+      confirm.swalFire({
+        title: 'Please wait...',
+        allowOutsideClick: false,
+        onBeforeOpen: () => {
+          Swal.showLoading();
+        },
+      });
       // Send paymentMethod.id to server
       var url = CRM.url('civicrm/stripe/confirm-payment');
       $.post(url, {
@@ -70,10 +85,15 @@
         id: CRM.vars.stripe.id,
         description: document.title,
         csrfToken: CRM.vars.stripe.csrfToken,
-      }).then(function (result) {
-        // Handle server response (see Step 3)
-        confirm.handleServerResponse(result);
-      });
+      })
+        .done(function (result) {
+          confirm.swalClose();
+          // Handle server response (see Step 3)
+          confirm.handleServerResponse(result);
+        })
+        .fail(function() {
+          confirm.swalClose();
+        });
     },
 
     checkAndLoad: function () {
@@ -103,6 +123,18 @@
           });
       }
     },
+
+    swalFire: function(parameters) {
+      if (typeof Swal === 'function') {
+        Swal.fire(parameters);
+      }
+    },
+
+    swalClose: function() {
+      if (typeof Swal === 'function') {
+        Swal.close();
+      }
+    }
   };
 
   if (typeof CRM.payment === 'undefined') {
diff --git a/js/civicrm_stripe.js b/js/civicrm_stripe.js
index 21ad1d3d9d8960d1e9e9408c9ec0ae179b2cac97..d324b87914cb5e85dd1ac0d6f0fbb8b6170b61f8 100644
--- a/js/civicrm_stripe.js
+++ b/js/civicrm_stripe.js
@@ -86,7 +86,7 @@ CRM.$(function($) {
     var errorElement = document.getElementById('card-errors');
     errorElement.style.display = 'block';
     errorElement.textContent = errorMessage;
-    form.dataset.submitted = false;
+    form.dataset.submitted = 'false';
     if (typeof submitButtons !== 'undefined') {
       for (i = 0; i < submitButtons.length; ++i) {
         submitButtons[i].removeAttribute('disabled');
@@ -152,6 +152,14 @@ CRM.$(function($) {
         }
         else {
           // Send paymentMethod.id to server
+          debugging('Waiting for pre-auth');
+          Swal.fire({
+            title: 'Please wait while we pre-authorize your card...',
+            allowOutsideClick: false,
+            onBeforeOpen: () => {
+              Swal.showLoading();
+            },
+          });
           var url = CRM.url('civicrm/stripe/confirm-payment');
           $.post(url, {
             payment_method_id: result.paymentMethod.id,
@@ -160,10 +168,14 @@ CRM.$(function($) {
             id: CRM.vars.stripe.id,
             description: document.title,
             csrfToken: CRM.vars.stripe.csrfToken,
-          }).then(function (result) {
-            // Handle server response (see Step 3)
-            handleServerResponse(result);
-          });
+          })
+            .done(function (result) {
+              // Handle server response (see Step 3)
+              handleServerResponse(result);
+            })
+            .always(function() {
+              Swal.close();
+            });
         }
       }
     });
@@ -270,6 +282,7 @@ CRM.$(function($) {
         dataType: 'script',
         cache: true,
         timeout: 5000,
+        crossDomain: true,
       })
         .done(function(data) {
           stripeLoading = false;
@@ -352,7 +365,7 @@ CRM.$(function($) {
 
     // If another submit button on the form is pressed (eg. apply discount)
     //  add a flag that we can set to stop payment submission
-    form.dataset.submitdontprocess = false;
+    form.dataset.submitdontprocess = 'false';
 
     // Find submit buttons which should not submit payment
     var nonPaymentSubmitButtons = form.querySelectorAll('[type="submit"][formnovalidate="1"], ' +
@@ -365,7 +378,7 @@ CRM.$(function($) {
 
     function submitDontProcess() {
       debugging('adding submitdontprocess');
-      form.dataset.submitdontprocess = true;
+      form.dataset.submitdontprocess = 'true';
     }
 
     for (i = 0; i < submitButtons.length; ++i) {
@@ -373,17 +386,13 @@ CRM.$(function($) {
     }
 
     function submitButtonClick(event) {
-      if (form.dataset.submitted === true) {
-        return;
-      }
-      form.dataset.submitted = true;
       // Take over the click function of the form.
       if (typeof CRM.vars.stripe === 'undefined') {
         // Submit the form
         return nonStripeSubmit();
       }
       debugging('clearing submitdontprocess');
-      form.dataset.submitdontprocess = false;
+      form.dataset.submitdontprocess = 'false';
 
       // Run through our own submit, that executes Stripe submission if
       // appropriate for this submit.
@@ -420,6 +429,11 @@ CRM.$(function($) {
       event.preventDefault();
       debugging('submit handler');
 
+      if (form.dataset.submitted === 'true') {
+        return;
+      }
+      form.dataset.submitted = 'true';
+
       if (($(form).valid() === false) || $(form).data('crmBillingFormValid') === false) {
         debugging('Form not valid');
         $('div#card-errors').hide();
@@ -462,11 +476,6 @@ CRM.$(function($) {
         return true;
       }
 
-      if (form.dataset.submitted === true) {
-        debugging('form already submitted');
-        return false;
-      }
-
       var stripeProcessorId = parseInt(CRM.vars.stripe.id);
       var chosenProcessorId = null;
 
@@ -510,7 +519,7 @@ CRM.$(function($) {
         return true;
       }
       // Don't handle submits generated by the CiviDiscount button.
-      if (form.dataset.submitdontprocess === true) {
+      if (form.dataset.submitdontprocess === 'true') {
         debugging('non-payment submit detected - not submitting payment');
         return true;
       }
@@ -538,16 +547,6 @@ CRM.$(function($) {
         return nonStripeSubmit();
       }
 
-      // Lock to prevent multiple submissions
-      if (form.dataset.submitted === true) {
-        // Previously submitted - don't submit again
-        alert('Form already submitted. Please wait.');
-        return false;
-      } else {
-        // Mark it so that the next submit can be ignored
-        form.dataset.submitted = true;
-      }
-
       // Disable the submit button to prevent repeated clicks
       for (i = 0; i < submitButtons.length; ++i) {
         submitButtons[i].setAttribute('disabled', true);
@@ -723,7 +722,7 @@ CRM.$(function($) {
         if (event.code === 'Enter') {
           event.preventDefault();
           debugging('adding submitdontprocess');
-          form.dataset.submitdontprocess = true;
+          form.dataset.submitdontprocess = 'true';
         }
     };