|
3 | 3 | var elements = {}; |
4 | 4 | var stripe_mode_validated = false; |
5 | 5 | var validated_stripe_pm_id = null; |
| 6 | + var stripe_card_state = { complete: false, error: null }; |
| 7 | + var stripe_validation_in_progress = false; |
6 | 8 | var ur_membership_frontend_utils = { |
7 | 9 | /** |
8 | 10 | * Appends a spinner element to the specified element. |
|
478 | 480 | }; |
479 | 481 |
|
480 | 482 | elements.card.emit("change", event); |
| 483 | + } else if (!stripe_card_state.complete) { |
| 484 | + // Card element has input but Stripe.js reports it |
| 485 | + // incomplete/invalid (e.g. missing postal code). |
| 486 | + // Block the submit here, before the registration |
| 487 | + // request creates the user, instead of letting |
| 488 | + // confirmCardPayment fail afterwards. |
| 489 | + no_errors = false; |
| 490 | + var incomplete_event = { |
| 491 | + error: { |
| 492 | + message: |
| 493 | + stripe_card_state.error && |
| 494 | + stripe_card_state.error.message |
| 495 | + ? stripe_card_state.error.message |
| 496 | + : urmf_data.labels |
| 497 | + .i18n_empty_card_details |
| 498 | + } |
| 499 | + }; |
| 500 | + |
| 501 | + elements.card.emit("change", incomplete_event); |
481 | 502 | } |
482 | 503 | } |
483 | 504 | }); |
|
1660 | 1681 | }); |
1661 | 1682 | }, |
1662 | 1683 | handle_authorize_multiple_purchase: function ( |
| 1684 | + submittedData, |
1663 | 1685 | selected_membership_id, |
1664 | 1686 | selected_pg, |
1665 | 1687 | btn, |
|
1681 | 1703 | action: "user_registration_membership_add_multiple_membership", |
1682 | 1704 | selected_membership_id: data.selected_membership_id, |
1683 | 1705 | selected_pg: data.selected_pg, |
1684 | | - ur_authorize_data: data.ur_authorize_data |
| 1706 | + ur_authorize_data: data.ur_authorize_data, |
| 1707 | + form_data: submittedData.form_data, |
| 1708 | + coupon: submittedData.coupon, |
| 1709 | + form_id: submittedData.form_id, |
| 1710 | + type: data.type |
1685 | 1711 | }, |
1686 | 1712 | { |
1687 | 1713 | success: function (response) { |
|
2157 | 2183 | elements.card.addEventListener("change", function (e) { |
2158 | 2184 | stripe_mode_validated = false; |
2159 | 2185 | validated_stripe_pm_id = null; |
| 2186 | + // Genuine Stripe change events always carry a boolean `complete`; |
| 2187 | + // synthetic events emitted from validate_membership_form don't, |
| 2188 | + // and must not overwrite the tracked card state. |
| 2189 | + if (typeof e.complete !== "undefined") { |
| 2190 | + stripe_card_state.complete = e.complete; |
| 2191 | + stripe_card_state.error = e.error || null; |
| 2192 | + } |
2160 | 2193 | if (e.error) { |
2161 | 2194 | stripe_settings.show_stripe_error(e.error.message); |
2162 | 2195 | } else { |
|
2269 | 2302 | prepare_members_data, |
2270 | 2303 | form_response |
2271 | 2304 | ); |
2272 | | - ur_membership_frontend_utils.hide_payment_processing_overlay(); |
2273 | 2305 | }); |
2274 | 2306 | }, |
2275 | 2307 | update_order_status: function ( |
|
3177 | 3209 | return; |
3178 | 3210 | } |
3179 | 3211 |
|
3180 | | - stripe_settings.show_stripe_error( |
3181 | | - urmf_data.labels.i18n_validating_stripe_card |
| 3212 | + // Prevent double submission |
| 3213 | + if (stripe_validation_in_progress) return; |
| 3214 | + stripe_validation_in_progress = true; |
| 3215 | + |
| 3216 | + var $submit_button = |
| 3217 | + $form && $form.length |
| 3218 | + ? $form.find(".ur-submit-button") |
| 3219 | + : $(".ur-submit-button"); |
| 3220 | + $submit_button.prop("disabled", true); |
| 3221 | + $submit_button |
| 3222 | + .find("span") |
| 3223 | + .addClass("ur-front-spinner"); |
| 3224 | + |
| 3225 | + // The core submit handler's `#stripe-errors:visible` guard is |
| 3226 | + // what stops this first submit from racing straight to |
| 3227 | + // registration while the card is still being validated (the |
| 3228 | + // button's own disabled state only protects *later* clicks, |
| 3229 | + // not this first synchronous pass). We still need that |
| 3230 | + // element to exist, but the user already sees the button's |
| 3231 | + // spinner, so this one is collapsed out of the layout |
| 3232 | + // entirely (zero size, taken out of normal flow) rather |
| 3233 | + // than just visually hidden — otherwise the `.user- |
| 3234 | + // registration-error` class's own border/padding still |
| 3235 | + // renders an empty box and reserves a gap. jQuery's |
| 3236 | + // `:visible` only requires the element to generate a box |
| 3237 | + // (any `getClientRects()` entry) — it doesn't need to have |
| 3238 | + // non-zero size — so this still satisfies that guard. |
| 3239 | + var $validating_marker = $( |
| 3240 | + '<label id="stripe-errors" class="user-registration-error" role="alert" style="position:absolute;width:0;height:0;margin:0;padding:0;border:0;overflow:hidden;"></label>' |
3182 | 3241 | ); |
| 3242 | + $membership_registration_form |
| 3243 | + .find(".stripe-container") |
| 3244 | + .closest(".ur_membership_frontend_input_container") |
| 3245 | + .append($validating_marker); |
| 3246 | + |
| 3247 | + // show_stripe_error() reuses an existing #stripe-errors |
| 3248 | + // element via .show(), which only toggles `display` and |
| 3249 | + // would leave the marker's collapsing inline styles in |
| 3250 | + // place — silently hiding a real error. Remove the marker |
| 3251 | + // first so a genuine failure always renders a fresh, |
| 3252 | + // fully-visible label. |
| 3253 | + var show_real_stripe_error = function (message) { |
| 3254 | + $membership_registration_form |
| 3255 | + .find("#stripe-errors") |
| 3256 | + .remove(); |
| 3257 | + stripe_settings.show_stripe_error(message); |
| 3258 | + }; |
| 3259 | + |
| 3260 | + var release_submit = function () { |
| 3261 | + stripe_validation_in_progress = false; |
| 3262 | + $submit_button.prop("disabled", false); |
| 3263 | + $submit_button |
| 3264 | + .find("span") |
| 3265 | + .removeClass("ur-front-spinner"); |
| 3266 | + }; |
3183 | 3267 |
|
3184 | 3268 | elements.stripe |
3185 | 3269 | .createPaymentMethod({ |
|
3188 | 3272 | }) |
3189 | 3273 | .then(function (pmResult) { |
3190 | 3274 | if (pmResult.error) { |
3191 | | - stripe_settings.show_stripe_error( |
| 3275 | + show_real_stripe_error( |
3192 | 3276 | pmResult.error.message |
3193 | 3277 | ); |
| 3278 | + release_submit(); |
3194 | 3279 | return; |
3195 | 3280 | } |
3196 | 3281 |
|
|
3209 | 3294 | validated_stripe_pm_id = |
3210 | 3295 | pmResult.paymentMethod.id; |
3211 | 3296 | stripe_mode_validated = true; |
| 3297 | + stripe_validation_in_progress = false; |
3212 | 3298 | if ($form && $form.length) { |
3213 | 3299 | $form |
3214 | 3300 | .find(".ur-submit-button") |
3215 | 3301 | .prop("disabled", false); |
3216 | 3302 | $form.submit(); |
3217 | 3303 | } |
3218 | 3304 | } else { |
3219 | | - stripe_settings.show_stripe_error( |
| 3305 | + show_real_stripe_error( |
3220 | 3306 | response.data && |
3221 | 3307 | response.data.message |
3222 | 3308 | ? response.data.message |
3223 | 3309 | : urmf_data.labels |
3224 | 3310 | .i18n_stripe_mode_error |
3225 | 3311 | ); |
| 3312 | + release_submit(); |
3226 | 3313 | } |
3227 | 3314 | } |
3228 | | - ); |
| 3315 | + ).fail(function () { |
| 3316 | + release_submit(); |
| 3317 | + }); |
| 3318 | + }) |
| 3319 | + .catch(function () { |
| 3320 | + release_submit(); |
3229 | 3321 | }); |
3230 | 3322 | } |
3231 | 3323 | ); |
|
0 commit comments