1( function ( $ ) { 2 let payments = null; 3 let walletPaymentRequest = null; 4 let paymentInProgress = false; 5 let initializationInProgress = false; 6 let sdkLoadPromise = null; 7 8 let alreadyInitialized = false; // Guard for initial load 9 10 // Debounce timer 11 let squareInitTimeout = null; 12 const DEBOUNCE_DELAY = 300; // Increased from 20ms to 300ms for better stability 13 14 // Cached jQuery selectors 15 const $cachedSelectors = {}; 16 17 // Helper to get cached jQuery selector 18 function getCached( selector ) { 19 if ( ! $cachedSelectors[ selector ] ) { 20 $cachedSelectors[ selector ] = $( selector ); 21 } 22 return $cachedSelectors[ selector ]; 23 } 24 25 // Clear cached selectors on checkout update 26 function clearSelectorCache() { 27 for ( const key in $cachedSelectors ) { 28 delete $cachedSelectors[ key ]; 29 } 30 } 31 32 // We remove lastKnownTotal logic & the booleans, allowing each partial refresh to re-init 33 34 /** 35 * Wait for Square SDK to be loaded 36 */ 37 function waitForSquareSDK() { 38 if ( sdkLoadPromise ) { 39 return sdkLoadPromise; 40 } 41 42 sdkLoadPromise = new Promise( ( resolve, reject ) => { 43 let attempts = 0; 44 const maxAttempts = 50; // 5 seconds total wait time 45 46 const checkSquare = () => { 47 if ( typeof Square !== 'undefined' && Square.payments ) { 48 resolve( Square ); 49 } else if ( attempts >= maxAttempts ) { 50 reject( 51 new Error( 'Square SDK failed to load after 5 seconds' ) 52 ); 53 } else { 54 attempts++; 55 setTimeout( checkSquare, 100 ); 56 } 57 }; 58 59 checkSquare(); 60 } ); 61 62 return sdkLoadPromise; 63 } 64 65 /** 66 * Debounces the Square init calls so multiple near-simultaneous triggers 67 * only cause a single actual initialization attempt. 68 */ 69 function scheduleSquareInit() { 70 if ( squareInitTimeout ) { 71 clearTimeout( squareInitTimeout ); 72 } 73 squareInitTimeout = setTimeout( handleSquareInit, DEBOUNCE_DELAY ); 74 } 75 76 /** 77 * Main initialization if squaresync_credit is chosen. 78 * Called after the debounce timer. 79 */ 80 async function handleSquareInit() { 81 // Prevent concurrent initialization attempts 82 if ( initializationInProgress ) { 83 console.log( 84 'Square initialization already in progress, skipping...' 85 ); 86 return; 87 } 88 89 const selectedMethod = $( 90 '#payment input[name="payment_method"]:checked' 91 ).val(); 92 if ( selectedMethod !== 'squaresync_credit' ) { 93 return; 94 } 95 96 initializationInProgress = true; 97 clearStoredTokens(); 98 99 try { 100 // Wait for Square SDK to be fully loaded 101 await waitForSquareSDK(); 102 console.log( 'Square SDK loaded successfully' ); 103 // Create 'payments' once if not done yet 104 if ( ! payments ) { 105 const applicationId = SquareConfig.applicationId; 106 const locationId = SquareConfig.locationId; 107 108 if ( ! applicationId || ! locationId ) { 109 throw new Error( 110 'Square configuration missing: applicationId or locationId' 111 ); 112 } 113 114 payments = await Square.payments( applicationId, locationId ); 115 console.log( 'Square payments instance created:', payments ); 116 if ( ! payments ) { 117 throw new Error( 'Square payments init failed' ); 118 } 119 } 120 121 const needsShipping = await checkNeedsShipping(); 122 const couponApplied = false; 123 // Build or refresh the PaymentRequest object 124 walletPaymentRequest = await initPaymentRequest( 125 payments, 126 needsShipping, 127 couponApplied 128 ); 129 130 // Show/hide card fields 131 toggleCardFields(); 132 133 // Only clean and reinitialize card if it doesn't exist or is broken 134 const cardContainer = document.getElementById( 'card-container' ); 135 const existingCardIframe = cardContainer 136 ? cardContainer.querySelector( 137 '.sq-card-iframe-container iframe' 138 ) 139 : null; 140 141 if ( ! existingCardIframe || ! window.squareCard ) { 142 // Card needs initialization 143 forceCleanCardContainer(); 144 initCreditCardPayment( payments, walletPaymentRequest ); 145 } else { 146 // Card already exists and is working, skip reinitialization 147 console.log( 148 'Credit card form already initialized, keeping existing instance' 149 ); 150 } 151 152 // Also forcibly remove leftover wallet buttons, then re-init 153 if ( walletPaymentRequest ) { 154 forceCleanWalletButtons(); 155 initWalletPayments( 156 payments, 157 walletPaymentRequest, 158 needsShipping, 159 couponApplied 160 ); 161 } 162 163 alreadyInitialized = true; 164 } catch ( err ) { 165 console.error( 'Square init error:', err ); 166 // Reset payments on error to allow retry 167 if ( err.message && err.message.includes( 'Square SDK' ) ) { 168 sdkLoadPromise = null; // Allow retry of SDK loading 169 } 170 logPaymentError( 171 'Payment system initialization failed. Please refresh the page.' 172 ); 173 } finally { 174 initializationInProgress = false; 175 } 176 } 177 178 /** 179 * Remove any leftover .sq-card-wrapper from partial refresh 180 * Only call this when you actually need to reinitialize the card 181 */ 182 function forceCleanCardContainer() { 183 const $container = $( '#payment #card-container' ); 184 if ( $container.length ) { 185 // Check if card instance exists and properly detach it 186 if ( 187 window.squareCard && 188 typeof window.squareCard.destroy === 'function' 189 ) { 190 try { 191 window.squareCard.destroy(); 192 window.squareCard = null; 193 console.log( 'Destroyed existing card instance' ); 194 } catch ( e ) { 195 console.warn( 'Error destroying card instance:', e ); 196 } 197 } 198 $container.find( '.sq-card-wrapper' ).remove(); 199 $container.find( '.sq-card-iframe-container' ).remove(); 200 } 201 } 202 203 /** 204 * Remove leftover wallet button elements 205 */ 206 function forceCleanWalletButtons() { 207 // Clean up wallet instances if cleanup function exists 208 if ( typeof cleanupWalletInstances === 'function' ) { 209 cleanupWalletInstances(); 210 } 211 212 // Don't empty Google Pay if button already exists 213 const googlePayExists = document.querySelector( 214 '#gpay-button-online-api-id' 215 ); 216 if ( ! googlePayExists ) { 217 jQuery( '#google-pay-button' ).empty(); 218 } 219 220 // Don't empty Afterpay if button already exists 221 const afterpayExists = document.querySelector( '.sq-ap__button' ); 222 if ( ! afterpayExists ) { 223 jQuery( '#afterpay-button' ).empty(); 224 } 225 226 // Apple Pay can be safely emptied as it doesn't persist 227 jQuery( '#apple-pay-button' ).empty(); 228 229 // Also re-allow initWalletPayments() if you use a guard inside it 230 // e.g. `walletPaymentsInitialized = false;` 231 window.walletPaymentsInitialized = false; 232 walletPaymentsInitialized = false; 233 } 234 235 /** 236 * Intercept place_order if squaresync_credit is chosen, so we can tokenize. 237 */ 238 $( document ).on( 'click', '#place_order', function ( e ) { 239 const method = $( 240 '#payment input[name="payment_method"]:checked' 241 ).val(); 242 if ( method !== 'squaresync_credit' || paymentInProgress ) return; 243 244 e.preventDefault(); 245 paymentInProgress = true; 246 $( document.body ).trigger( 'checkout_place_order_squaresync_credit' ); 247 } ); 248 249 /** 250 * Actually tokenize the card, store nonce, then re-submit. 251 */ 252 $( document.body ).on( 253 'checkout_place_order_squaresync_credit', 254 async function () { 255 // Clear any stale nonces before generating a new one 256 clearStoredTokens(); 257 console.log( 258 'Starting payment processing - cleared any stale nonces' 259 ); 260 261 const success = await processCreditCardPayment( 262 payments, 263 walletPaymentRequest 264 ); 265 paymentInProgress = false; 266 if ( success ) { 267 if ( SquareConfig.context === 'account' ) { 268 $( '#add_payment_method' ).trigger( 'submit' ); 269 } else { 270 // Handle both checkout and pay-for-order forms 271 const $form = $( 'form.checkout, form#order_review' ); 272 if ( $form.length ) { 273 $form.trigger( 'submit' ); 274 } else { 275 console.error( 276 'Could not find checkout form to submit' 277 ); 278 } 279 } 280 } else { 281 console.log( 'Payment processing failed - nonces cleared' ); 282 clearStoredTokens(); 283 } 284 } 285 ); 286 287 $( document.body ).on( 'wc_payment_method_selected', scheduleSquareInit ); 288 289 /** 290 * Show/hide the new card fields vs. saved tokens 291 */ 292 function toggleCardFields() { 293 const token = $( 294 '#payment input[name="wc-squaresync_credit-payment-token"]:checked' 295 ).val(); 296 if ( token && token !== 'new' ) { 297 $( '#payment-form' ).hide(); 298 $( '.woocommerce-SavedPaymentMethods-saveNew' ).hide(); 299 } else { 300 $( '#payment-form' ).show(); 301 $( '.woocommerce-SavedPaymentMethods-saveNew' ).show(); 302 } 303 } 304 305 // Debounced triggers from WooCommerce 306 $( document.body ).on( 'updated_checkout', function ( e, data ) { 307 // Check if this is a minor update (like coupon application) 308 const isMinorUpdate = 309 data && ( data.coupon_applied || data.coupon_removed ); 310 311 if ( ! isMinorUpdate ) { 312 clearSelectorCache(); 313 } 314 315 // Always schedule init but it will skip if card already exists 316 scheduleSquareInit(); 317 } ); 318 $( document.body ).on( 319 'change', 320 '#payment input[name="payment_method"]', 321 function () { 322 // Payment method changed, need full reinit 323 clearSelectorCache(); 324 scheduleSquareInit(); 325 } 326 ); 327 328 $( document ).on( 329 'click', 330 '#wc-squaresync_credit-payment-token-new', 331 function ( e ) { 332 $( '#payment-form' ).show(); 333 // Initialize Square when switching to new payment method 334 // Only trigger on pay-for-order pages where updated_checkout doesn't fire 335 const method = $( 336 '#payment input[name="payment_method"]:checked' 337 ).val(); 338 const isPayForOrderPage = $( 'body' ).hasClass( 339 'woocommerce-order-pay' 340 ); 341 if ( method === 'squaresync_credit' && isPayForOrderPage ) { 342 scheduleSquareInit(); 343 } 344 } 345 ); 346 $( document ).on( 347 'click', 348 '.woocommerce-SavedPaymentMethods-token input', 349 function ( e ) { 350 $( '#payment-form' ).hide(); 351 } 352 ); 353 354 // If user picks "Use a new card," re-check 355 // $(document).on('change', '#payment input[name="wc-squaresync_credit-payment-token"]', function () { 356 // toggleCardFields(); 357 // const method = $('#payment input[name="payment_method"]:checked').val(); 358 // if ($(this).val() === 'new' && method === 'squaresync_credit') { 359 // forceCleanCardContainer() 360 // forceCleanWalletButtons() 361 // scheduleSquareInit(); 362 // } 363 // }); 364 365 // On account page "Add Payment Method", do an init once 366 $( document ).ready( function () { 367 if ( SquareConfig.context === 'account' ) { 368 $( document.body ).trigger( 'wc_payment_method_selected' ); 369 } 370 371 // On pay-for-order pages, initialize Square if the payment method is already selected 372 const isPayForOrderPage = $( 'body' ).hasClass( 373 'woocommerce-order-pay' 374 ); 375 const isSquareSelected = $( '#payment_method_squaresync_credit' ).is( 376 ':checked' 377 ); 378 if ( isPayForOrderPage && isSquareSelected ) { 379 scheduleSquareInit(); 380 } 381 } ); 382 383 // On checkout_error, clear tokens and fully re-initialize card + wallets 384 $( document.body ).on( 'checkout_error', function () { 385 try { 386 console.log( 387 'checkout_error event fired - clearing nonces and reinitializing' 388 ); 389 clearStoredTokens(); 390 // Clean existing UI/instances to avoid stale nonce or stuck state 391 forceCleanCardContainer(); 392 forceCleanWalletButtons(); 393 // Re-run initialization to mount fresh instances 394 scheduleSquareInit(); 395 paymentInProgress = false; 396 } catch ( e ) { 397 console.warn( 'Square re-init after checkout_error failed:', e ); 398 } 399 } ); 400 401 // Additional handler: Reset on form submission failure (defense-in-depth for customized checkouts) 402 // This catches cases where checkout_error might not fire consistently 403 let formSubmissionAttempted = false; 404 $( document ).on( 405 'submit', 406 'form.checkout, form#order_review', 407 function () { 408 const method = $( 409 '#payment input[name="payment_method"]:checked' 410 ).val(); 411 if ( method === 'squaresync_credit' ) { 412 formSubmissionAttempted = true; 413 console.log( 'Form submission attempted with Square payment' ); 414 } 415 } 416 ); 417 418 // Monitor for WooCommerce error notices appearing after form submission 419 const observer = new MutationObserver( function ( mutations ) { 420 mutations.forEach( function ( mutation ) { 421 if ( mutation.addedNodes.length ) {
422 mutation.addedNodes.forEach( function ( node ) { 423 // Check if error notices were added to the page 424 if ( 425 node.nodeType === 1 && 426 ( $( node ).hasClass( 'woocommerce-error' ) || 427 $( node ).hasClass( 428 'woocommerce-NoticeGroup-checkout' 429 ) || 430 $( node ).find( '.woocommerce-error' ).length ) 431 ) { 432 const method = $( 433 '#payment input[name="payment_method"]:checked' 434 ).val(); 435 if ( 436 formSubmissionAttempted && 437 method === 'squaresync_credit' 438 ) { 439 console.log( 440 'Payment error detected via notice - clearing nonces for retry' 441 ); 442 clearStoredTokens(); 443 paymentInProgress = false; 444 formSubmissionAttempted = false; 445 // Card instance should remain for retry, but nonces are cleared 446 } 447 } 448 } ); 449 } 450 } ); 451 } ); 452 453 // Start observing the checkout form area for error notices 454 $( document ).ready( function () { 455 const checkoutForm = document.querySelector( 456 '.woocommerce-checkout, .woocommerce-order-pay' 457 ); 458 if ( checkoutForm ) { 459 observer.observe( checkoutForm, { 460 childList: true, 461 subtree: true, 462 } ); 463 } 464 465 // Clear stale nonces when page is shown from browser cache (back button) 466 window.addEventListener( 'pageshow', function ( event ) { 467 if ( event.persisted ) { 468 // Page was loaded from cache (back/forward button) 469 console.log( 470 'Page shown from cache - clearing any stale payment nonces' 471 ); 472 clearStoredTokens(); 473 paymentInProgress = false; 474 formSubmissionAttempted = false; 475 } 476 } ); 477 } ); 478 479 // Handle coupon-specific events to prevent card destruction 480 $( document.body ).on( 'applied_coupon removed_coupon', function ( e ) { 481 console.log( 'Coupon event detected, preserving card fields' ); 482 // Just update payment request without destroying card 483 if ( payments && walletPaymentRequest ) { 484 checkNeedsShipping().then( ( needsShipping ) => { 485 initPaymentRequest( payments, needsShipping, true ).then( 486 ( updatedRequest ) => { 487 if ( updatedRequest ) { 488 walletPaymentRequest = updatedRequest; 489 console.log( 490 'Payment request updated for coupon change' 491 ); 492 } 493 } 494 ); 495 } ); 496 } 497 } ); 498} )( 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.