Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by NeutronCollector395

Why Does JavaScript Stop Running on Resubmit After a Stripe Card Decline in Laravel Cashier?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm adding memberships to our web app and testing different cards to handle errors. When I simulate a card decline with a Stripe test card, I receive a declined response, and then the form where card info is entered cannot be re-submitted, as it appears that JavaScript stops running (the click event on the button isn’t triggered).

Below is the relevant Blade file that includes the payment element and accompanying JavaScript code:

HTML
<!DOCTYPE html> <html> <head> @include('layouts.redesign.head') <script src="https://js.stripe.com/v3/"></script> <style> #overlay { position: fixed; top: 0; z-index: 100; width: 100%; height: 100%; display: none; background: rgba(0, 0, 0, 0.6); } .cv-spinner { height: 100%; display: flex; justify-content: center; align-items: center; } .spinner { width: 40px; height: 40px; border: 4px #ddd solid; border-top: 4px #2e93e6 solid; border-radius: 50%; animation: sp-anime 0.8s infinite linear; } @keyframes sp-anime { 100% { transform: rotate(360deg); } } .is-hide { display: none; } </style> </head> <body class="mad-body--scheme-brown"> <div id="mad-page-wrapper" class="mad-page-wrapper"> @include('layouts.redesign.header') <!--================ Page Title ================--> <div class="mad-breadcrumb with-bg-img with-overlay light bg-alignright" data-bg-image-src="{{asset('/images/banner-service-consultations.jpg')}}"> <div class="container wide"> <h1 class="mad-page-title">Sign up</h1> </div> </div> <div class="container my-3"> <form method="GET" id="signupForm" class="mad-contact-form mad-form type-2 item-col-1 m-auto" style="max-width: 500px"> <div class="mad-form-item"> <label for="email">Name:</label> <input type="text" id="name" name="name" required placeholder="Name"/> </div> <div class="mad-form-item"> <label for="email">Email:</label> <input type="text" id="email" name="email" required placeholder="Email"/> </div> <div class="mad-form-item"> <label for="password">Password:</label> <input type="password" id="password" name="password" required placeholder="Password"/> </div> <div class="mad-form-item"> <label for="password_confirmation">Confirm Password:</label> <input type="password" id="password_confirmation" name="password_confirmation" required placeholder="Confirm Password"/> </div> <div class="mad-col full-width"> <div class="mad-row full-width row"> <span id="errors"></span> <div class="mad-form-item align-center"> <button type="submit" class="btn btn-big g-recaptcha create_user" >{{__('contact_form.submit')}} </button> </div> </div> </div> </form> <div class="stripe_lmnts mad-contact-form mad-form type-2 item-col-1 m-auto" style="display: none; max-width: 500px"> <!-- Stripe Elements Placeholder --> <div id="payment-element"></div> <button id="card-button" class="btn btn-primary py-3" data-secret="{{$client_secret}}"> Sign up </button> </div> </div> <div id="overlay"> <div class="cv-spinner"> <span class="spinner"></span> </div> </div> <script> $(document).ready(function () { let user_id; $('.create_user').on('click', function (e) { e.preventDefault() $.ajax({ url: '{{route('create_user')}}', type: 'GET', data: { name: $('input[name="name"]').val(), email: $('input[name="email"]').val(), password: $('input[name="password"]').val(), password_confirmation: $('input[name="password_confirmation"]').val(), }, success: function (data) { let response = JSON.parse(data) if (!response.error) { user_id = response.user_id $('.stripe_lmnts').css('display', 'block') $('#signupForm').css('display', 'none') } else { $('#errors').html(response.message) if (response.for === 'email') { $('input[name="email"]').css('border-color', '#dc3545') } if (response.for === 'password') { $('input[name="password"]').css('border-color', '#dc3545') $('input[name="password_confirmation"]').css('border-color', '#dc3545') } } } }) }) const stripe = Stripe('{{env('STRIPE_KEY')}}'); const cardButton = $('#card-button'); const clientSecret = cardButton.data('secret'); const options = {layout: 'accordion', /* options */}; const appearance = { theme: 'flat', variables: {colorPrimaryText: '#262626'} }; const elements = stripe.elements({clientSecret, appearance}); const paymentElement = elements.create('payment', options); paymentElement.mount('#payment-element'); cardButton.on('click', function () { $(this).prop('disabled', true) $(this).addClass('btn-secondary') $(this).removeClass('btn-primary') $("#overlay").fadeIn(300); stripe.confirmSetup({ elements, confirmParams: { // Return URL where the customer should be redirected after the PaymentIntent is confirmed. return_url: `http://localhost:8080/create-subscription?user_id=${user_id}`, }, }) .then(function (result) { if (result.error) { $(this).prop('disabled', false) $(this).removeClass('btn-secondary') $(this).addClass('btn-primary') $("#overlay").fadeOut(300); } }); }) }) </script> </div> @include('layouts.redesign.footer') </body> </html>

I suspect the issue may relate to how the callback in the stripe.confirmSetup promise handles 'this'. When a card is declined, the function inside the .then() block uses a regular function, causing 'this' to lose reference to the button element. As shown in some answers, using an arrow function or storing the button reference outside the promise can resolve the problem. My goal is to re-enable the button after a decline so that users can retry the payment. Any additional insights or fixes would be appreciated.

0

Comments 0

Answers 2

Views 32

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is one common explanation for why your button stops working after a decline error: the callback inside your stripe.confirmSetup promise is using a regular function and referencing "this," which no longer points to the button element. When the Stripe test card returns a decline error, the code in the then() block runs, but since "this" isn’t bound as expected inside that callback, calling methods like $(this).prop('disabled', false) silently fails (or throws an error) and can cause subsequent click events not to fire.

To fix the issue, you can store a reference to your button outside the promise or use an arrow function (which lexically binds "this"). For example, you can modify your cardButton click handler as follows:

JS
cardButton.on('click', function () { const $btn = $(this); $btn.prop('disabled', true); $btn.addClass('btn-secondary').removeClass('btn-primary'); $("#overlay").fadeIn(300); stripe.confirmSetup({ elements, confirmParams: { return_url: `http://localhost:8080/create-subscription?user_id=${user_id}`, }, }) .then((result) => { // using arrow function so that $btn remains in scope if (result.error) { $btn.prop('disabled', false); $btn.removeClass('btn-secondary').addClass('btn-primary'); $("#overlay").fadeOut(300); // You can also display a user-friendly error message here using result.error.message } }); });

This way, if a payment is declined, your code correctly re-enables the button and restores the styling, so that your users can retry the payment.

A couple of additional points:

  • Make sure to check your browser console for any JavaScript errors. An unhandled error could stop further JS execution.
  • This issue isn’t directly caused by Stripe’s API but rather by how the error result is handled in your JavaScript.
  • In general, when handling async responses (like from Stripe’s confirmSetup) ensure that your callbacks operate on the correct context.

I hope this helps resolve the issue!

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by NebulousCaptain900 3 months ago

0

I would recommend you focus on debugging the error-handling then block after confirmSetup:

JAVASCRIPT
.then(function (result) { if (result.error) { $(this).prop('disabled', false) $(this).removeClass('btn-secondary') $(this).addClass('btn-primary') $("#overlay").fadeOut(300); } });

You can verify whether this from your own overlay/disable by removing that treatment before calling confirmSubmit, too. Comment this out:

JAVASCRIPT
$(this).prop('disabled', true) $(this).addClass('btn-secondary') $(this).removeClass('btn-primary') $("#overlay").fadeIn(300);

No comments yet.

Discussion

No comments yet.