1( function( $ ) { 2 3 $(document).ready( function() { 4 5 if ( ! leaky_paywall_stripe_registration_ajax.stripe_pk ) { 6 return; 7 } 8 9 // Bail cleanly if Stripe.js was blocked. This happens when a consent 10 // management platform (Cookiebot, OneTrust, Iubenda, etc.) auto-blocks 11 // https://js.stripe.com/v3/ because the visitor didn't consent to the 12 // category the CMP classified Stripe under. Without this guard the 13 // form throws "Stripe is not defined" and the Subscribe button appears 14 // broken â user has no idea their consent choices are the reason. 15 // 16 // Publisher-side fix: mark Stripe as "Necessary" in the consent tool 17 // (Stripe processes the transaction the visitor is trying to make, 18 // which fits the Necessary category under GDPR). We surface an 19 // in-form message here so the visitor knows what to do. 20 if ( typeof Stripe === 'undefined' ) { 21 var $errors = $('#leaky-paywall-registration-errors'); 22 if ( $errors.length ) { 23 $errors.html( 24 '<div class="leaky_paywall_message error">' + 25 'Payment processing is currently blocked by your cookie settings. ' + 26 'To subscribe, please reopen the cookie banner and allow the payment / necessary category, then reload this page.' + 27 '</div>' 28 ); 29 } 30 // Also disable the submit buttons so the user doesn't repeatedly 31 // click a form that can't submit. 32 $('#checkout, #leaky-paywall-registration-next').prop('disabled', true); 33 return; 34 } 35 36 let stripe = Stripe(leaky_paywall_stripe_registration_ajax.stripe_pk); 37 38 if ( leaky_paywall_stripe_registration_ajax.client_id ) { 39 stripe = Stripe(leaky_paywall_stripe_registration_ajax.stripe_pk, { stripeAccount: leaky_paywall_stripe_registration_ajax.client_id}); 40 } 41 42 let elements; 43 let billingAddress = null; 44 let isSubmitting = false; 45 46 // Stripe Checkout 47 $('#checkout').click(function(e) { 48 e.preventDefault(); 49 $(this).text(leaky_paywall_stripe_registration_ajax.continue_text); 50 $("#leaky-paywall-registration-errors").html(""); 51 validateUserData(); 52 }); 53 54 // one time and recurring 55 $("#leaky-paywall-registration-next").click(function () { 56 $(this).text(leaky_paywall_stripe_registration_ajax.continue_text).prop('disabled', true); 57 $("#leaky-paywall-registration-errors").html(""); 58 validateUserData(); 59 }); 60 61 // Intercept Enter key during step 1 (account setup) â prevents native form submission 62 // which would bypass AJAX validation and fail server-side with no payment intent. 63 document.querySelector("#leaky-paywall-payment-form") && document.querySelector("#leaky-paywall-payment-form").addEventListener("submit", function(e) { 64 if ($(".leaky-paywall-form-account-setup-step").hasClass("active")) { 65 e.preventDefault(); 66 $("#leaky-paywall-registration-next").text(leaky_paywall_stripe_registration_ajax.continue_text); 67 $("#leaky-paywall-registration-errors").html(""); 68 validateUserData(); 69 } 70 }); 71 72 73 function validateUserData() { 74 75 $("#leaky-paywall-registration-errors").html(""); 76 77 const form_data = $("#leaky-paywall-payment-form").serialize(); 78 79 const data = { 80 action: "leaky_paywall_process_user_registration_validation", 81 form_data: form_data, 82 nonce: leaky_paywall_stripe_registration_ajax.register_nonce 83 }; 84 85 $.post(leaky_paywall_stripe_registration_ajax.ajaxurl, data, function (resp) { 86 87 if (resp.errors) { 88 $.each(resp.errors, function (i, value) { 89 $("#leaky-paywall-registration-errors").append( 90 "<p class='leaky-paywall-registration-error'>" + 91 value.message + 92 "</p>" 93 ); 94 $("#leaky-paywall-registration-next").text( leaky_paywall_stripe_registration_ajax.next_text ).prop('disabled', false); 95 $("#leaky-paywall-registration-errors").show(); 96 97 $("html, body").animate( 98 { 99 scrollTop: $(".leaky-paywall-registration-error").offset().top, 100 }, 101 1000 102 ); 103 }); 104 105 } else { 106 107 if ( resp.subscription_updated ) { 108 // Existing subscription was updated to new plan, submit form directly 109 let form$ = jQuery('#leaky-paywall-payment-form'); 110 form$.get(0).submit(); 111 } else if ( resp.session_id ) { 112 // stripe checkout
113 stripe.redirectToCheckout({ 114 sessionId: resp.session_id 115 }); 116 } else if (resp.pi_client) { 117 // one time payment 118 $("#payment-intent-client").val(resp.pi_client); 119 $("#payment-intent-id").val(resp.pi_id); 120 121 buildPaymentForm(resp.pi_client); 122 showPaymentForm(); 123 124 // Handle form submission. 125 document.querySelector("#leaky-paywall-payment-form").addEventListener("submit", handleSubmit); 126 127 } else if (resp.deferred_tax) { 128 // subscription with automatic tax â defer subscription creation until address is entered 129 $("#stripe-customer-id").val(resp.customer_id); 130 buildAddressOnly(); 131 showPaymentForm(); 132 133 } else if (resp.customer_id) { 134 // subscription 135 $("#stripe-customer-id").val(resp.customer_id); 136 137 buildPaymentForm(resp.client_secret); 138 showPaymentForm(); 139 140 // Handle form submission. 141 document.querySelector("#leaky-paywall-payment-form").addEventListener("submit", handleSubmit); 142 143 } 144 145 } 146 147 }); 148 149 }; // end validate user data 150 151 let addressElementMounted = false; 152 153 function mountAddressElement( elementsInstance ) { 154 const addressElement = elementsInstance.create("address", { 155 mode: "billing", 156 }); 157 158 addressElement.mount("#address-element"); 159 addressElementMounted = true; 160 161 addressElement.on('change', (event) => { 162 if (event.complete) { 163 billingAddress = event.value; 164 $('input[name="lp_billing_name"]').val(billingAddress.name || ''); 165 $('input[name="lp_billing_line1"]').val(billingAddress.address.line1 || ''); 166 $('input[name="lp_billing_line2"]').val(billingAddress.address.line2 || ''); 167 $('input[name="lp_billing_city"]').val(billingAddress.address.city || ''); 168 $('input[name="lp_billing_state"]').val(billingAddress.address.state || ''); 169 $('input[name="lp_billing_postal_code"]').val(billingAddress.address.postal_code || ''); 170 $('input[name="lp_billing_country"]').val(billingAddress.address.country || ''); 171 172 if ( 'on' === leaky_paywall_stripe_registration_ajax.automatic_tax_enabled ) { 173 fetchTaxPreview(billingAddress.address); 174 } 175 } 176 }); 177 } 178 179 function buildAddressOnly() { 180 // For deferred tax flow: mount only the address element first, without a clientSecret. 181 var standaloneElements = stripe.elements(); 182 mountAddressElement( standaloneElements ); 183 } 184 185 function buildPaymentForm( clientSecret ) { 186 187 elements = stripe.elements({ clientSecret }); 188 189 const paymentElementOptions = { 190 layout: "tabs", 191 }; 192 193 const paymentElement = elements.create("payment", paymentElementOptions); 194 195 // Temporarily lock scroll position while Stripe Element mounts to prevent layout shift. 196 var scrollPos = $(window).scrollTop(); 197 paymentElement.mount("#payment-element"); 198 $(window).scrollTop(scrollPos); 199 200 if ( 'on' == leaky_paywall_stripe_registration_ajax.billing_address && !addressElementMounted ) { 201 mountAddressElement( elements ); 202 } 203 204 } 205 206 function showPaymentForm() { 207 208 setTimeout(() => { 209 $("#leaky-paywall-registration-errors").hide(); 210 $("#leaky-paywall-registration-next").remove(); 211 $(".leaky-paywall-registration-user-container").hide(); 212 $(".leaky-paywall-form-payment-setup-step").addClass("active"); 213 $(".leaky-paywall-form-account-setup-step").removeClass("active"); 214 215 $(".leaky-paywall-registration-payment-container").slideDown(); 216 217 if ( 'on' === leaky_paywall_stripe_registration_ajax.automatic_tax_enabled ) { 218 showInitialOrderSummary(); 219 } 220 221 $("html, body").animate( 222 { 223 scrollTop: $(".leaky-paywall-form-steps").offset().top, 224 }, 225 1000 226 ); 227 }, 500); 228 229 } 230 231 function showInitialOrderSummary() { 232 var price = $('input[name="level_price"]').val(); 233 var currency = $('input[name="currency"]').val() || 'USD'; 234 var formatter = formatCurrency(currency); 235 var subtotal = Math.round(parseFloat(price) * 100); 236 237 $('#lp-order-subtotal-amount').text(formatter(subtotal)); 238 $('#lp-order-tax-amount').text('â').addClass('lp-order-pending'); 239 $('#lp-order-total-amount').text('â'); 240 $('#lp-order-summary').slideDown(); 241 } 242 243 var taxPreviewRequest = null; 244 245 function fetchTaxPreview(address) { 246 var levelId = $('input[name="level_id"]').val(); 247 var customerId = $('#stripe-customer-id').val() || ''; 248 249 // Abort any in-flight request. 250 if ( taxPreviewRequest ) { 251 taxPreviewRequest.abort(); 252 } 253 254 $('#lp-order-tax-amount').text('Calculating...').addClass('lp-order-pending'); 255 $('#leaky-paywall-submit').prop('disabled', true); 256 257 taxPreviewRequest = $.ajax({ 258 url: leaky_paywall_stripe_registration_ajax.ajaxurl, 259 type: 'POST', 260 dataType: 'json', 261 data: { 262 action: 'leaky_paywall_stripe_tax_preview', 263 nonce: leaky_paywall_stripe_registration_ajax.tax_preview_nonce, 264 level_id: levelId, 265 customer_id: customerId, 266 line1: address.line1 || '', 267 city: address.city || '', 268 state: address.state || '', 269 postal_code: address.postal_code || '', 270 country: address.country || '' 271 }, 272 success: function(response) { 273 jQuery('#leaky-paywall-submit').prop('disabled', false); 274 if (response.success) { 275 var data = response.data; 276 var formatter = formatCurrency(data.currency); 277 jQuery('#lp-order-subtotal-amount').text(formatter(data.subtotal)); 278 jQuery('#lp-order-tax-amount').text(formatter(data.tax)).removeClass('lp-order-pending'); 279 jQuery('#lp-order-total-amount').text(formatter(data.total)); 280 281 // Build payment form now that subscription is created with tax. 282 if (data.client_secret && !jQuery('#payment-element .StripeElement, #payment-element iframe').length) { 283 buildPaymentForm(data.client_secret); 284 document.querySelector("#leaky-paywall-payment-form").addEventListener("submit", handleSubmit); 285 } 286 } else { 287 jQuery('#lp-order-tax-amount').text('$0.00').removeClass('lp-order-pending'); 288 } 289 }, 290 error: function(xhr, status, err) { 291 jQuery('#leaky-paywall-submit').prop('disabled', false); 292 jQuery('#lp-order-tax-amount').text('$0.00').removeClass('lp-order-pending'); 293 } 294 }); 295 } 296 297 function formatCurrency(currency) { 298 return function(amountInCents) { 299 return new Intl.NumberFormat(undefined, { 300 style: 'currency', 301 currency: currency 302 }).format(amountInCents / 100); 303 }; 304 } 305 306 307 async function handleSubmit(e) { 308 e.preventDefault(); 309 310 // Re-entry guard: if a submission is already in flight, ignore further submits 311 // (handles rapid double-click / Enter-spam where the disabled attribute isn't 312 // applied between events). 313 if ( isSubmitting ) { 314 return; 315 } 316 isSubmitting = true; 317 318 let subButton = document.getElementById('leaky-paywall-submit'); 319 if ( subButton ) { 320 subButton.disabled = true; 321 subButton.innerHTML = leaky_paywall_stripe_registration_ajax.continue_text; 322 } 323 324 let emailAddress = $('input[name="email_address"]').val(); 325 let isTrial = $('input[name="is_trial"]').val(); 326 let paymentMethod = $('input[name="payment_method"]:checked').val(); 327 328 if ( paymentMethod != 'stripe' ) { 329 let form$ = jQuery('#leaky-paywall-payment-form'); 330 form$.get(0).submit(); 331 return; 332 } 333 334 if ( isTrial ) { 335 const { error }
335 = await stripe.confirmSetup({ 336 elements, 337 confirmParams: { 338 return_url: leaky_paywall_stripe_registration_ajax.redirect_url, 339 }, 340 redirect: 'if_required' 341 }); 342 343 if ( error ) { 344 if (error.type === "card_error" || error.type === "validation_error") { 345 showMessage(error.message); 346 } else { 347 showMessage("An unexpected error occurred."); 348 } 349 resetSubButton(); 350 } else { 351 let form$ = jQuery('#leaky-paywall-payment-form'); 352 form$.get(0).submit(); 353 } 354 355 } else { 356 const { error, paymentIntent } = await stripe.confirmPayment({ 357 elements, 358 confirmParams: { 359 // Make sure to change this to your payment completion page 360 return_url: leaky_paywall_stripe_registration_ajax.redirect_url, 361 receipt_email: emailAddress, 362 }, 363 redirect: 'if_required' 364 }); 365 366 if (paymentIntent && paymentIntent.id) { 367 368 // 'processing' is the success state for ACH (us_bank_account) â the bank 369 // transfer is in flight but won't settle for 3-5 business days. Treat it 370 // as a success so the subscriber lands on the post-signup page (where the 371 // ACH-pending modal can fire) instead of being silently dropped here. 372 if ( paymentIntent.status === 'succeeded' || paymentIntent.status === 'processing' ) { 373 let form$ = jQuery('#leaky-paywall-payment-form'); 374 form$.get(0).submit(); 375 } else { 376 resetSubButton(); 377 } 378 379 } 380 381 if ( error ) { 382 if (error.type === "card_error" || error.type === "validation_error") { 383 showMessage(error.message); 384 } else { 385 showMessage("An unexpected error occurred."); 386 } 387 resetSubButton(); 388 } 389 } 390 391 // This point will only be reached if there is an immediate error when 392 // confirming the payment. Otherwise, your customer will be redirected to 393 // your `return_url`. For some payment methods like iDEAL, your customer will 394 // be redirected to an intermediate site first to authorize the payment, then 395 // redirected to the `return_url`. 396 397 398 399 400 // setLoading(false); 401 } 402 403 function showMessage(messageText) { 404 const messageContainer = document.querySelector("#payment-message"); 405 406 messageContainer.classList.remove("hidden"); 407 messageContainer.textContent = messageText; 408 409 setTimeout(function () { 410 messageContainer.classList.add("hidden"); 411 messageContainer.textContent = ""; 412 }, 7000); 413 } 414 415 function resetSubButton() { 416 isSubmitting = false; 417 let subButton = document.getElementById('leaky-paywall-submit'); 418 if ( subButton ) { 419 subButton.disabled = false; 420 subButton.innerHTML = 'Subscribe'; 421 } 422 } 423 424 425 }); // doc ready 426 427})( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.