1/** 2 * Unified Button Manager pentru Fake Checkout 3 * Versiunea 2.1.0 - Production Ready (log cleaned) 4 * 5 * Acest manager unificat este singurul sistem de toggle butoane: 6 * - fake-checkout-toggle.js (removed) 7 * - CheckoutButtonManager din form-checkout.js (removed in Task #011) 8 * - toggleCheckoutButtons din fake-checkout-handler.js (consolidated in Task #011) 9 * - Colaboreaza cu Horeka Core pentru validare suma minima 10 * 11 * Caracteristici: 12 * - Un singur punct de control pentru toate butoanele 13 * - FÄrÄ race conditions sau conflicte 14 * - Debouncing pentru performanÈÄ 15 * - Minimal logging pentru producÈie 16 */ 17 18(function($) { 19 'use strict'; 20 21 // ======================================== 22 // CONFIG ÈI CONSTANTE 23 // ======================================== 24 25 // Logging pentru producÈie (dezactivat implicit) 26 const ENABLE_LOGGING = false; 27 const log = function(message, data) { 28 if (ENABLE_LOGGING) { 29 if (data) { 30 console.log(message, data); 31 } else { 32 console.log(message); 33 } 34 } 35 }; 36 37 const CONFIG = { 38 // Debounce delays pentru evitarea apelurilor multiple 39 DEBOUNCE_DELAY: 250, // Delay pentru toggle buttons 40 INIT_DELAY: 500, // Delay pentru iniÈializare 41 42 // Selectori DOM 43 SELECTORS: { 44 normalButton: '#place_order', 45 fakeButton: '.fake-checkout-button, #place_order_fake', 46 paymentRadios: 'input[name="payment_method"]', 47 paymentDropdown: 'select[name="payment_method"]', 48 blueCards: '.blue-cards label', 49 body: 'body', 50 iframe: '#iframeCheckout' 51 }, 52 53 // Clase CSS pentru detectare 54 CLASSES: { 55 fakeCheckoutMode: 'fake-checkout-mode', 56 inIframe: 'in-iframe-checkout', 57 netopiaSelected: 'netopia-payment-selected' 58 }, 59 60 // Metode de platÄ 61 PAYMENT_METHODS: { 62 netopia: 'netopiapayments', 63 cod: 'cod', 64 cardOnDelivery: 'WC_Card_Livrare_Payment_Gateway' 65 } 66 }; 67 68 // ======================================== 69 // MANAGER PRINCIPAL 70 // ======================================== 71 window.UnifiedButtonManager = { 72 // State intern 73 state: { 74 isInitialized: false, 75 currentPaymentMethod: null, 76 isFakeCheckoutMode: false, 77 isNetopiaSelected: false, 78 lastToggleTime: 0, 79 toggleCount: 0, 80 // SOLUÈIE PAYMENT PERSISTENCE: SalvÄm alegerea utilizatorului 81 userSelectedPaymentMethod: null, // Metoda aleasÄ explicit de utilizator 82 isRestoringPayment: false, // Flag pentru a evita salvarea în timpul restaurÄrii 83 hasUserMadeSelection: false // Flag pentru a detecta dacÄ utilizatorul a fÄcut o selecÈie explicitÄ 84 }, 85 86 /** 87 * IniÈializeazÄ managerul 88 * Aceasta este singura metodÄ care trebuie apelatÄ 89 */ 90 init: function() { 91 const self = this; 92 93 // Auto-fix pointer-events:none on fake checkout button so clicks register. 94 document.addEventListener('click', function(e) { 95 var fakeBtn = document.querySelector('.fake-checkout-button'); 96 if (!fakeBtn) { 97 return; 98 } 99 var rect = fakeBtn.getBoundingClientRect(); 100 var clickX = e.clientX, clickY = e.clientY; 101 if (clickX >= rect.left && clickX <= rect.right && clickY >= rect.top && clickY <= rect.bottom) { 102 if (window.getComputedStyle(fakeBtn).pointerEvents === 'none') { 103 fakeBtn.style.pointerEvents = 'auto'; 104 fakeBtn.style.opacity = '1'; 105 fakeBtn.style.cursor = 'pointer'; 106 fakeBtn.disabled = false; 107 fakeBtn.classList.remove('button-disabled'); 108 } 109 } 110 }, true); 111 112 // Previne iniÈializarea dublÄ 113 if (this.state.isInitialized) { 114 return; 115 } 116 117 // SOLUÈIE CRITICÄ: CurÄÈÄ localStorage la prima încÄrcare 118 // Doar dacÄ utilizatorul nu a fÄcut încÄ o selecÈie explicitÄ Ã®n aceastÄ sesiune 119 this.clearInitialPaymentState(); 120 121 // DetecteazÄ contextul iniÈial 122 this.detectContext(); 123 124 // ConfigureazÄ event listeners 125 this.setupEventListeners(); 126 127 // IniÈializare cu delay pentru a permite încÄrcarea completÄ a DOM 128 setTimeout(function() { 129 self.checkInitialState(); 130 self.state.isInitialized = true; 131 log('[UnifiedManager] â Manager iniÈializat cu succes'); 132 }, CONFIG.INIT_DELAY); 133 }, 134 135 /** 136 * SOLUÈIE CRITICÄ: CurÄÈÄ starea iniÈialÄ de payment pentru prima vizitare 137 * Previne restaurarea automatÄ din localStorage la prima încÄrcare a paginii 138 */ 139 clearInitialPaymentState: function() { 140 // VerificÄ dacÄ avem un marker cÄ utilizatorul a fÄcut o selecÈie în aceastÄ sesiune 141 const sessionKey = 'horeka_user_made_payment_selection_' + Date.now().toString().slice(-8);
142 const hasSessionSelection = sessionStorage.getItem('horeka_user_made_selection'); 143 144 if (!hasSessionSelection) { 145 // Prima încÄrcare în aceastÄ sesiune de browser - curÄÈÄ localStorage 146 try { 147 const oldValue = localStorage.getItem('horeka_user_payment_method'); 148 if (oldValue) { 149 localStorage.removeItem('horeka_user_payment_method'); 150 log('[UnifiedManager] ð§¹ Prima încÄrcare - Èters localStorage payment method:', oldValue); 151 } 152 } catch (e) { 153 // Ignore localStorage errors 154 } 155 156 // MarcheazÄ cÄ am curÄÈat starea iniÈialÄ 157 try { 158 sessionStorage.setItem('horeka_session_cleaned', 'true'); 159 } catch (e) { 160 // Ignore sessionStorage errors 161 } 162 163 log('[UnifiedManager] ð Prima încÄrcare detectatÄ - utilizatorul va selecta fresh'); 164 } else { 165 log('[UnifiedManager] ð Sesiune continuÄ - pÄstrez preferinÈele utilizatorului'); 166 this.state.hasUserMadeSelection = true; 167 } 168 }, 169 170 /** 171 * DetecteazÄ contextul (iframe, fake checkout mode, lite version) 172 */ 173 detectContext: function() { 174 const $body = $(CONFIG.SELECTORS.body); 175 const $iframe = $(CONFIG.SELECTORS.iframe); 176 177 // VerificÄ dacÄ suntem în iframe 178 const inIframe = (window !== window.top); 179 180 // VerificÄ lite version din PHP config 181 const isLiteVersion = window.unifiedManagerConfig && window.unifiedManagerConfig.isLiteVersion; 182 183 // VerificÄ dacÄ avem fake checkout mode (iframe SAU lite version) 184 this.state.isFakeCheckoutMode = 185 $body.hasClass(CONFIG.CLASSES.fakeCheckoutMode) || 186 $iframe.length > 0 || 187 document.cookie.indexOf('is_iframe_context=1') > -1 || 188 inIframe || 189 isLiteVersion; 190 191 log('[UnifiedManager] ð Context detectat:', { 192 inIframe: inIframe, 193 isLiteVersion: isLiteVersion, 194 fakeCheckoutMode: this.state.isFakeCheckoutMode, 195 hasIframeElement: $iframe.length > 0, 196 hasCookie: document.cookie.indexOf('is_iframe_context=1') > -1 197 }); 198 199 return this.state.isFakeCheckoutMode; 200 }, 201 202 /** 203 * VerificÄ starea iniÈialÄ Èi seteazÄ butoanele corect 204 */ 205 checkInitialState: function() { 206 // GÄseÈte metoda de platÄ selectatÄ 207 const $selectedPayment = $(CONFIG.SELECTORS.paymentRadios + ':checked'); 208 const $dropdown = $(CONFIG.SELECTORS.paymentDropdown); 209 210 let currentMethod = null; 211 212 if ($selectedPayment.length) { 213 currentMethod = $selectedPayment.val(); 214 } else if ($dropdown.length) { 215 currentMethod = $dropdown.val(); 216 } 217 218 if (currentMethod) { 219 log('[UnifiedManager] ð Stare iniÈialÄ - metodÄ platÄ:', currentMethod); 220 this.state.currentPaymentMethod = currentMethod; 221 this.updateButtons(); 222 } else { 223 log('[UnifiedManager] â ï¸ Nicio metodÄ de platÄ selectatÄ iniÈial'); 224 } 225 }, 226 227 /** 228 * ConfigureazÄ toÈi event listeners 229 * Un singur loc pentru toate evenimentele 230 */ 231 setupEventListeners: function() { 232 const self = this; 233 234 // IMPORTANT: CurÄÈÄm TOATE event listeners existente înainte de a adÄuga noi 235 $(document).off('change', CONFIG.SELECTORS.paymentRadios); 236 $(document).off('change', CONFIG.SELECTORS.paymentDropdown); 237 $(document).off('click', CONFIG.SELECTORS.blueCards); 238 $(document.body).off('updated_checkout.unifiedManager'); 239 $(document).off('payment_method_visibility_updated.unifiedManager'); 240 241 log('[UnifiedManager] ð§§ Event listeners curÄÈate pentru a preveni duplicarea'); 242 243 // Debounce function pentru a preveni apelurile multiple 244 const debouncedUpdate = this.debounce(function() { 245 self.updateButtons(); 246 }, CONFIG.DEBOUNCE_DELAY); 247 248 // ======================================== 249 // EVENIMENTE PENTRU SCHIMBAREA METODEI DE PLÄTÄ 250 // ======================================== 251 252 // 1. Radio buttons standard - cu namespace pentru identificare uÈoarÄ 253 $(document).on('change.unifiedManager', CONFIG.SELECTORS.paymentRadios, function() { 254 const method = $(this).val();
255 log('[UnifiedManager] ð» Radio schimbat:', method); 256 self.state.currentPaymentMethod = method; 257 258 // SOLUÈIE PAYMENT PERSISTENCE: SalvÄm alegerea utilizatorului 259 // SalvÄm DOAR dacÄ nu suntem în proces de restaurare Èi utilizatorul a fÄcut o selecÈie explicitÄ 260 if (!self.state.isRestoringPayment) { 261 // MarcÄm cÄ utilizatorul a fÄcut o selecÈie explicitÄ (nu automatÄ) 262 self.state.hasUserMadeSelection = true; 263 self.saveUserPaymentChoice(method); 264 log('[UnifiedManager] ð¾ MetodÄ salvatÄ pentru restaurare (selecÈie utilizator):', method); 265 } 266 267 debouncedUpdate(); 268 }); 269 270 // 2. Dropdown select (dacÄ existÄ) - cu namespace 271 $(document).on('change.unifiedManager', CONFIG.SELECTORS.paymentDropdown, function() { 272 const method = $(this).val(); 273 log('[UnifiedManager] ð Dropdown schimbat:', method); 274 self.state.currentPaymentMethod = method; 275 276 // SOLUÈIE PAYMENT PERSISTENCE: SalvÄm Èi pentru dropdown 277 if (!self.state.isRestoringPayment) { 278 self.state.hasUserMadeSelection = true; 279 self.saveUserPaymentChoice(method); 280 log('[UnifiedManager] ð¾ MetodÄ salvatÄ pentru restaurare (dropdown):', method); 281 } 282 283 debouncedUpdate(); 284 }); 285 286 // 3. Blue cards click (pentru design custom) - cu namespace 287 $(document).on('click.unifiedManager', CONFIG.SELECTORS.blueCards, function() { 288 const method = $(this).find('input').val(); 289 log('[UnifiedManager] ð³ Card selectat:', method); 290 self.state.currentPaymentMethod = method; 291 292 // SOLUÈIE PAYMENT PERSISTENCE: SalvÄm Èi pentru blue cards 293 if (!self.state.isRestoringPayment) { 294 self.state.hasUserMadeSelection = true; 295 self.saveUserPaymentChoice(method); 296 log('[UnifiedManager] ð¾ MetodÄ salvatÄ pentru restaurare (blue card):', method); 297 } 298 299 // Delay mic pentru a permite radio sÄ se selecteze 300 setTimeout(function() { 301 debouncedUpdate(); 302 }, 50); 303 }); 304 305 // ======================================== 306 // EVENIMENTE WOOCOMMERCE 307 // ======================================== 308 309 // 4. DupÄ update checkout - cu namespace 310 $(document.body).on('updated_checkout.unifiedManager', function() { 311 log('[UnifiedManager] â»ï¸ Checkout actualizat'); 312 self.detectContext(); // Re-verificÄ contextul 313 314 // SOLUÈIE PAYMENT PERSISTENCE: RestaurÄm metoda de platÄ salvatÄ 315 // ÃMBUNÄTÄÈIRE TIMING: RestaurÄm IMEDIAT pentru a preveni auto-selecÈia 316 setTimeout(function() { 317 // PRIORITATE: ÃncercÄm sÄ restaurÄm metoda salvatÄ de utilizator PRIMUL 318 self.restoreUserPaymentChoice(); 319 320 // DupÄ restaurare, verificÄm starea iniÈialÄ 321 self.checkInitialState(); 322 }, 10); // Redus de la 100ms la 10ms pentru timing mai rapid 323 }); 324 325 // 4b. ÃMBUNÄTÄÈIRE ANTI-RESET: SalveazÄ metoda curentÄ ÃNAINTE de update_checkout 326 $(document.body).on('update_checkout.unifiedManager', function() { 327 log('[UnifiedManager] â° Pre-update: Salvez metoda curentÄ...'); 328 // SalvÄm rapid metoda curent selectatÄ Ã®nainte ca checkout-ul sÄ se actualizeze 329 var currentMethod = $('input[name="payment_method"]:checked').val(); 330 if (currentMethod) { 331 self.state.userSelectedPaymentMethod = currentMethod; 332 self.state.hasUserMadeSelection = true; 333 log('[UnifiedManager] ð¾ Pre-salvat pentru backup:', currentMethod); 334 } 335 }); 336 337 // 5. Custom event pentru update vizibilitate - cu namespace 338 $(document).on('payment_method_visibility_updated.unifiedManager', function() { 339 log('[UnifiedManager] ðï¸ Vizibilitate actualizatÄ'); 340 debouncedUpdate(); 341 }); 342 343 // ======================================== 344 // EVENIMENTE PENTRU METODA DE LIVRARE 345 // ======================================== 346 347 // 6. Schimbarea metodei de livrare (radio buttons) 348 $(document).on('change.unifiedManager', 'input[name="delivery_method"]', function() { 349 const method = $(this).val(); 350 log('[UnifiedManager] ð MetodÄ livrare schimbatÄ:', method); 351 352 // Re-verificÄ suma minimÄ când se schimbÄ metoda de livrare 353 setTimeout(function() { 354 self.updateButtons(); 355 }, 100); 356 }); 357 358 // 7. Schimbarea metodei de livrare (dropdown) 359 $(document).on('change.unifiedManager', 'select[name="delivery_method"]', function() { 360 const method = $(this).val(); 361 log('[UnifiedManager] ð MetodÄ livrare schimbatÄ (dropdown):', method); 362 363 // Re-verificÄ suma minimÄ când se schimbÄ metoda de livrare 364 setTimeout(function() { 365 self.updateButtons(); 366 }, 100); 367 }); 368 369 // 8. Schimbarea adresei de livrare sau facturare 370 $(document).on('change.unifiedManager blur.unifiedManager', 371 '#billing_address_1_field input, #shipping_address_1_field input, input[name="billing_address_1"], input[name="shipping_address_1"]', 372 function() { 373 const addressValue = $(this).val(); 374 log('[UnifiedManager] ð AdresÄ schimbatÄ:', addressValue); 375 376 // Delay mai mare pentru a permite calculul taxelor de livrare 377 setTimeout(function() { 378 self.updateButtons(); 379 }, 300); 380 }); 381 382 // 9. Update când se schimbÄ totalul comenzii (pentru re-validare) 383 $(document.body).on('updated_cart_totals updated_checkout updated_shipping_method.unifiedManager', function() { 384 log('[UnifiedManager] ð° Totaluri actualizate - re-verific suma minimÄ'); 385 setTimeout(function() { 386 self.updateButtons(); 387 }, 200); 388 }); 389 390 // ======================================== 391 // DEZACTIVARE SISTEME VECHI 392 // ======================================== 393 394 // OpreÈte alte managere sÄ intervinÄ 395 this.disableConflictingSystems(); 396 }, 397 398 /** 399 * Ensures the fake checkout button exists in the DOM. 400 * Creates it dynamically next to #place_order if missing. 401 * Previously this logic lived in fake-checkout-handler.js toggleCheckoutButtons(). 402 * Consolidated here in Task #011. 403 */ 404 ensureFakeButtonExists: function() { 405 var $fakeButton = $(CONFIG.SELECTORS.fakeButton); 406 if ($fakeButton.length > 0) { 407 return $fakeButton; 408 } 409 410 var $normalButton = $(CONFIG.SELECTORS.normalButton); 411 if (!$normalButton.length) { 412 return $(); 413 } 414 415 var buttonText = $normalButton.text() || 'Plaseaza comanda'; 416 var fakeButtonHtml = '<button type="button" class="button alt fake-checkout-button" id="place_order_fake" style="display: none;" data-fake-checkout="true">' + buttonText + '</button>'; 417 $normalButton.after(fakeButtonHtml); 418 $fakeButton = $('#place_order_fake'); 419 log('[UnifiedManager] Fake button created dynamically'); 420 421 return $fakeButton; 422 }, 423 424 /** 425 * FuncÈia principalÄ care actualizeazÄ butoanele 426 * Aceasta este singura funcÈie care manipuleazÄ DOM-ul 427 */ 428 updateButtons: function() { 429 // Re-detecteazÄ contextul la fiecare update 430 this.detectContext(); 431 432 const $normalButton = $(CONFIG.SELECTORS.normalButton); 433 434 // Ensure fake button exists before trying to use it 435 if (this.state.isFakeCheckoutMode && 436 this.state.currentPaymentMethod === CONFIG.PAYMENT_METHODS.netopia) { 437 this.ensureFakeButtonExists(); 438 } 439 440 const $fakeButton = $(CONFIG.SELECTORS.fakeButton); 441 442 // VerificÄ dacÄ butoanele existÄ 443 if (!$normalButton.length && !$fakeButton.length) { 444 return; 445 } 446 447 // ======================================== 448 // VERIFICARE SUMA MINIMÄ DE COMANDÄ 449 // ======================================== 450 451 // VerificÄ dacÄ suntem în modul redirect fake checkout SAU fake-checkout-mode (light app)
452 const urlParams = new URLSearchParams(window.location.search); 453 const isRedirectMode = urlParams.get('auto_submit') === '1' || 454 urlParams.get('fake_checkout') === '1' || 455 $('body').hasClass('fake-checkout-mode'); 456 457 log('[UnifiedManager] updateButtons() - isRedirectMode:', isRedirectMode, 'fake-checkout-mode class:', $('body').hasClass('fake-checkout-mode')); 458 459 // VerificÄ metoda de livrare ÃNAINTE de validarea sumei minime 460 const deliveryMethod = this.getDeliveryMethod(); 461 462 // Pentru ridicare personalÄ, NU verifica suma minimÄ deloc 463 if (deliveryMethod === 'pickup') { 464 log('[UnifiedManager] ðª Ridicare personalÄ - skip complet validare sumÄ minimÄ'); 465 } else if (isRedirectMode) { 466 // Ãn modul redirect pentru livrare acasÄ, NU verifica suma minimÄ 467 log('[UnifiedManager] ð Redirect mode livrare acasÄ - skip validare sumÄ minimÄ'); 468 } else { 469 // Doar pentru livrare acasÄ Ã®n mod normal, verificÄ suma minimÄ 470 log('[UnifiedManager] ð Livrare acasÄ mod normal - verific suma minimÄ'); 471 const validationPassed = this.checkMinimumOrderAmount(); 472 473 // DacÄ validarea nu a trecut, butoanele rÄmân disable Èi ieÈim 474 if (!validationPassed) { 475 log('[UnifiedManager] â ï¸ Suma minimÄ nu este atinsÄ - butoane disabled'); 476 return; 477 } 478 } 479 480 // VERIFICARE PRIORITARÄ: DacÄ Horeka Core a disabled butoanele, respectÄ decizia 481 if (this.state.buttonsDisabledByHoreka) { 482 log('[UnifiedManager] â¸ï¸ Butoane disabled de Horeka Core - nu aplic toggle logic'); 483 return; // NU schimba nimic, lasÄ Horeka Core sÄ controleze 484 } 485 486 // DeterminÄ dacÄ trebuie sÄ arÄtÄm butonul fake 487 const shouldShowFake = 488 this.state.isFakeCheckoutMode && 489 this.state.currentPaymentMethod === CONFIG.PAYMENT_METHODS.netopia && 490 !this.hasAddressValidationErrors(); // ADÄUGAT: Verificare erori adresÄ 491 492 // Logging doar dacÄ starea se schimbÄ 493 if (this.shouldLog()) { 494 log('[UnifiedManager] ð Toggle butoane:', { 495 metodaPlata: this.state.currentPaymentMethod, 496 fakeMode: this.state.isFakeCheckoutMode, 497 showFake: shouldShowFake, 498 hasAddressErrors: this.hasAddressValidationErrors(), 499 redirectMode: isRedirectMode 500 }); 501 } 502 503 // ======================================== 504 // APLICARE STARE - SIMPLU ÈI CLAR 505 // ======================================== 506 507 if (shouldShowFake) { 508 // AratÄ butonul fake, ascunde pe cel normal - FORÈAT cu CSS inline 509 $normalButton 510 .css({ 511 'display': 'none', 512 'visibility': 'hidden', 513 'position': 'absolute', 514 'left': '-9999px' 515 }) 516 .addClass('button-hidden') 517 .removeClass('button-visible'); 518 519 $fakeButton 520 .css({ 521 'display': 'inline-block', 522 'visibility': 'visible', 523 'position': 'relative', 524 'left': 'auto', 525 'pointer-events': 'auto', 526 'opacity': '1', 527 'cursor': 'pointer' 528 }) 529 .prop('disabled', false) 530 .addClass('button-visible') 531 .removeClass('button-hidden button-disabled'); 532 533 log('[UnifiedManager] updateButtons() - fake button ENABLED & SHOWN'); 534 535 // AdaugÄ clase pe body pentru CSS 536 $(CONFIG.SELECTORS.body).addClass(CONFIG.CLASSES.netopiaSelected); 537 538 // DEZACTIVAT: Nu salva automat în localStorage - doar pentru redirect iframe specific 539 // localStorage.setItem('paymentMethod', CONFIG.PAYMENT_METHODS.netopia); 540 541 log('[UnifiedManager] â Buton FAKE activat pentru Netopia'); 542 } else { 543 // AratÄ butonul normal, ascunde pe cel fake - FORÈAT cu CSS inline 544 $normalButton 545 .css({ 546 'display': 'inline-block', 547 'visibility': 'visible', 548 'position': 'relative', 549 'left': 'auto' 550 }) 551 .addClass('button-visible') 552 .removeClass('button-hidden'); 553 554 $fakeButton 555 .css({ 556 'display': 'none', 557 'visibility': 'hidden', 558 'position': 'absolute', 559 'left': '-9999px' 560 }) 561 .addClass('button-hidden') 562 .removeClass('button-visible'); 563 564 // EliminÄ clase de pe body 565 $(CONFIG.SELECTORS.body).removeClass(CONFIG.CLASSES.netopiaSelected); 566 567 // CurÄÈÄ localStorage 568 localStorage.removeItem('paymentMethod'); 569 570 if (this.shouldLog()) { 571 log('[UnifiedManager] â Buton NORMAL activat'); 572 } 573 } 574 575 // Update state pentru tracking 576 this.state.toggleCount++; 577 }, 578 579 /** 580 * VerificÄ suma minimÄ de comandÄ 581 * ColaboreazÄ REAL cu validarea din Horeka Core 582 */ 583 checkMinimumOrderAmount: function() { 584 // 1. VerificÄ mai întâi tipul de livrare 585 const deliveryMethod = this.getDeliveryMethod(); 586 587 // Pentru ridicare personalÄ (pickup), nu verificÄ suma minimÄ 588 if (deliveryMethod === 'pickup') { 589 log('[UnifiedManager] ðª Ridicare personalÄ - nu se verificÄ suma minimÄ'); 590 this.enableButtons(); 591 return true; 592 } 593 594 // 2. Pentru livrare acasÄ, verificÄ cookie-ul CanDeliver setat de Horeka Core 595 log('[UnifiedManager] ð Livrare acasÄ - verific cookie CanDeliver'); 596 597 var canDeliver = (this.getCookie('CanDeliver') || '').toLowerCase(); 598 599 if (canDeliver === 'false') { 600 log('[UnifiedManager] ð CanDeliver=False - butoane disabled (Horeka Core decision)'); 601 this.disableButtons(); 602 return false;
603 } 604 605 if (canDeliver === 'true' || canDeliver === '') { 606 log('[UnifiedManager] â CanDeliver OK - butoane enabled'); 607 this.enableButtons(); 608 return true; 609 } 610 611 // Cookie absent (prima încÄrcare) â Horeka Core va seta cookie-ul la updated_checkout 612 log('[UnifiedManager] â³ Cookie CanDeliver absent - permit trecerea, aÈtept Horeka Core'); 613 return true; 614 }, 615 616 /** 617 * DeterminÄ metoda de livrare selectatÄ 618 */ 619 getDeliveryMethod: function() { 620 // CautÄ radio buttons pentru metoda de livrare 621 const $deliveryRadio = $('input[name="delivery_method"]:checked'); 622 if ($deliveryRadio.length) { 623 const method = $deliveryRadio.val(); 624 625 // MapeazÄ valorile posibile la categorii 626 if (method === 'ridicare-personala' || method === 'servire-la-restaurant' || method === 'pickup') { 627 return 'pickup'; 628 } else if (method === 'livrare-la-domiciliu' || method === 'home_delivery') { 629 return 'home_delivery'; 630 } 631 return method; // ReturneazÄ valoarea exactÄ dacÄ nu se potriveÈte 632 } 633 634 // CautÄ Ã®n dropdown 635 const $deliverySelect = $('select[name="delivery_method"]'); 636 if ($deliverySelect.length) { 637 const method = $deliverySelect.val(); 638 log('[UnifiedManager] ð MetodÄ livrare detectatÄ (select):', method); 639 640 // MapeazÄ valorile posibile 641 if (method === 'ridicare-personala' || method === 'servire-la-restaurant' || method === 'pickup') { 642 return 'pickup'; 643 } else if (method === 'livrare-la-domiciliu' || method === 'home_delivery') { 644 return 'home_delivery'; 645 } 646 return method; 647 } 648 649 // VerificÄ Ã®n body class sau alte indicii 650 if ($(document.body).hasClass('pickup-delivery')) { 651 log('[UnifiedManager] ð MetodÄ livrare detectatÄ (body class): pickup'); 652 return 'pickup'; 653 } 654 655 // Debug - aratÄ toate input-urile de delivery pentru diagnosticare 656 const $allDeliveryInputs = $('input[name*="delivery"], select[name*="delivery"]'); 657 if ($allDeliveryInputs.length) { 658 log('[UnifiedManager] ð§ Debug - toate input-urile delivery gÄsite:'); 659 $allDeliveryInputs.each(function() { 660 log(' -', $(this).attr('name'), '=', $(this).val(), 'checked:', $(this).is(':checked')); 661 }); 662 } 663 664 // Default: presupune cÄ e livrare acasÄ 665 log('[UnifiedManager] â ï¸ Nu s-a putut determina metoda de livrare - presupun livrare acasÄ'); 666 return 'home_delivery'; 667 }, 668 669 /** 670 * VerificÄ dacÄ existÄ erori de validare adresÄ 671 * @returns {boolean} true dacÄ existÄ erori de adresÄ 672 */ 673 hasAddressValidationErrors: function() { 674 // VerificÄ mesajele de eroare WooCommerce 675 const $errors = $('.woocommerce-error, .woocommerce-message--error'); 676 if ($errors.length > 0) { 677 const errorText = $errors.text().toLowerCase(); 678 if (errorText.includes('nu putem livra') ||
679 errorText.includes('cannot deliver') || 680 errorText.includes('select an address')) { 681 log('[UnifiedManager] â Eroare validare adresÄ detectatÄ:', errorText); 682 return true; 683 } 684 } 685 686 // VerificÄ cookie-ul CanDeliver (poate fi 'false' cu F mic sau 'False' cu F mare) 687 const canDeliver = this.getCookie('CanDeliver'); 688 if (canDeliver === 'false' || canDeliver === 'False') { 689 log('[UnifiedManager] â Cookie CanDeliver =', canDeliver); 690 // Ascunde secÈiunea place-order când adresa nu e validÄ 691 $('.form-row.place-order').hide(); 692 return true; 693 } else if (canDeliver === 'true' || canDeliver === 'True' || canDeliver === '') { 694 // AfiÈeazÄ secÈiunea place-order când adresa e validÄ sau neutrÄ 695 $('.form-row.place-order').show(); 696 } 697 698 return false; 699 }, 700 701 /** 702 * ObÈine valoarea unui cookie 703 * @param {string} name Numele cookie-ului 704 * @returns {string} Valoarea cookie-ului sau string gol 705 */ 706 getCookie: function(name) { 707 const nameEQ = name + "="; 708 // FIX: Split raw cookie string FIRST, then decode only the matched value. 709 // decodeURIComponent(document.cookie) breaks if ANY cookie has a raw '%'. 710 const cookies = document.cookie.split(';'); 711 712 for (let i = 0; i < cookies.length; i++) { 713 let c = cookies[i].trim(); 714 if (c.indexOf(nameEQ) === 0) { 715 const rawValue = c.substring(nameEQ.length); 716 try { 717 return decodeURIComponent(rawValue); 718 } catch (e) { 719 // Value contains malformed percent-encoding; return as-is 720 return rawValue; 721 } 722 } 723 } 724 return ""; 725 }, 726 727 /** 728 * Validare fallback pentru suma minimÄ când Horeka Core nu e disponibil 729 */ 730 validateMinimumOrderFallback: function() { 731 // CautÄ suma totalÄ Ã®n DOM 732 const $totalElement = $('.order-total .woocommerce-Price-amount, .total .amount'); 733 734 if (!$totalElement.length) { 735 log('[UnifiedManager] â ï¸ Nu s-a gÄsit elementul cu suma totalÄ'); 736 return true; // LasÄ sÄ treacÄ dacÄ nu gÄsim suma 737 } 738 739 // Extrage valoarea numericÄ din text 740 const totalText = $totalElement.text(); 741 const totalValue = parseFloat(totalText.replace(/[^\d,.-]/g, '').replace(',', '.')); 742 743 // Suma minimÄ default (poate fi configuratÄ) 744 const minimumAmount = (window.unifiedManagerConfig && window.unifiedManagerConfig.minimumOrderAmount) 745 ? parseFloat(window.unifiedManagerConfig.minimumOrderAmount) 746 : 50; 747 748 log('[UnifiedManager] ð° Validare fallback:', { 749 totalText: totalText, 750 totalValue: totalValue, 751 minimumAmount: minimumAmount, 752 isValid: totalValue >= minimumAmount 753 }); 754 755 if (totalValue >= minimumAmount) { 756 return true; 757 } else { 758 log('[UnifiedManager] ð Suma minimÄ nu e atinsÄ - disable silent fÄrÄ popup'); 759 return false; 760 } 761 }, 762 763 /** 764 * AfiÈeazÄ popup-ul pentru suma minimÄ (colaboreazÄ cu Horeka Core) 765 */ 766 showMinimumOrderPopup: function() { 767 // DacÄ Horeka Core are funcÈia pentru popup, o foloseÈte 768 if (typeof window.showMinimumOrderPopup === 'function') { 769 log('[UnifiedManager] ð¢ AfiÈez popup Horeka Core pentru suma minimÄ'); 770 window.showMinimumOrderPopup(); 771 return; 772 } 773 774 // Fallback: popup simplu 775 const message = 'Suma minimÄ pentru comandÄ nu este atinsÄ. Te rugÄm sÄ adaugi mai multe produse.'; 776 777 // VerificÄ dacÄ avem sweet alert sau alt sistem 778 if (typeof Swal !== 'undefined') { 779 Swal.fire({ 780 icon: 'warning', 781 title: 'SumÄ minimÄ necesarÄ', 782 text: message, 783 confirmButtonText: 'OK' 784 }); 785 } else { 786 // Fallback la alert browser 787 alert(message); 788 } 789 790 log('[UnifiedManager] ð¢ Am afiÈat popup fallback pentru suma minimÄ'); 791 }, 792 793 /** 794 * DezactiveazÄ butoanele când suma minimÄ nu e atinsÄ 795 */ 796 disableButtons: function() { 797 log('[UnifiedManager] ð« disableButtons() called'); 798 const $normalButton = $(CONFIG.SELECTORS.normalButton); 799 const $fakeButton = $(CONFIG.SELECTORS.fakeButton); 800 801 // Disable Èi stilizeazÄ butoanele 802 $normalButton 803 .prop('disabled', true) 804 .css({ 805 'opacity': '0.5', 806 'cursor': 'not-allowed', 807 'pointer-events': 'none' 808 }) 809 .addClass('button-disabled'); 810 811 $fakeButton 812 .prop('disabled', true) 813 .css({ 814 'opacity': '0.5', 815 'cursor': 'not-allowed', 816 'pointer-events': 'none' 817 }) 818 .addClass('button-disabled'); 819 820 log('[UnifiedManager] ð« Butoane disabled pentru sumÄ minimÄ'); 821 }, 822 823 /** 824 * ActiveazÄ butoanele când suma minimÄ este atinsÄ 825 */ 826 enableButtons: function() { 827 log('[UnifiedManager] â enableButtons() called'); 828 const $normalButton = $(CONFIG.SELECTORS.normalButton); 829 const $fakeButton = $(CONFIG.SELECTORS.fakeButton); 830 831 // Enable Èi reseteazÄ stilurile 832 $normalButton 833 .prop('disabled', false) 834 .css({ 835 'opacity': '1', 836 'cursor': 'pointer', 837 'pointer-events': 'auto' 838 }) 839 .removeClass('button-disabled'); 840 841 $fakeButton 842 .prop('disabled', false) 843 .css({ 844 'opacity': '1', 845 'cursor': 'pointer', 846 'pointer-events': 'auto' 847 }) 848 .removeClass('button-disabled'); 849 850 log('[UnifiedManager] â Butoane enabled - suma minimÄ atinsÄ'); 851 }, 852 853 /** 854 * ConfigureazÄ colaborarea perfectÄ cu Horeka Core 855 * SoluÈie hibridÄ: Horeka Core executÄ disable/enable, UnifiedManager sincronizeazÄ 856 */ 857 disableConflictingSystems: function() { 858 const self = this; 859 860 // SETEAZÄ FLAGS GLOBALE pentru autoritate partajatÄ 861 window.UNIFIED_MANAGER_IS_ACTIVE = true; 862 window.BUTTON_MANAGER_ACTIVE = 'hybrid'; 863 window.BUTTON_CONTROL_AUTHORITY = 'UnifiedManager+HorekaCore'; 864 865 log('[UnifiedManager] ð¤ Configurez colaborare hibridÄ cu Horeka Core'); 866 867 // STRATEGIA HIBRIDÄ: Permit Horeka Core sÄ facÄ disable/enable REAL 868 // dar sincronizez UnifiedManager cu aceste schimbÄri 869 870 // 1. NU bloca horekaButtonControlDisabled - permite Horeka Core sÄ lucreze 871 // Acest flag rÄmâne false pentru a permite Horeka Core sÄ execute logica 872 setTimeout(function() { 873 if (typeof window.horekaButtonControlDisabled !== 'undefined') { 874 // FORÈEZ false pentru a permite Horeka Core sÄ funcÈioneze 875 window.horekaButtonControlDisabled = false;
876 log('[UnifiedManager] ð Permit Horeka Core sÄ controleze butoanele (horekaButtonControlDisabled = false)'); 877 } 878 }, 100); 879 880 // 2. INTERCEPTARE INTELIGENTÄ: Horeka Core executÄ, UnifiedManager sincronizeazÄ 881 if (typeof window.disablePlaceOrderButton === 'function') { 882 const originalDisable = window.disablePlaceOrderButton; 883 window.disablePlaceOrderButton.unifiedManagerOriginal = originalDisable; 884 885 window.disablePlaceOrderButton = function(reason) { 886 log('[UnifiedManager] ð¨ Horeka Core cere disable butoane:', reason); 887 888 // 1. ExecutÄ ORIGINAL Horeka Core disable logic 889 originalDisable.apply(this, arguments); 890 891 // 2. Sincronizez UnifiedManager state 892 if (self && self.state) { 893 self.state.buttonsDisabledByHoreka = true; 894 self.state.horekaDisableReason = reason; 895 } 896 897 // 3. Apply UnifiedManager styling pentru consistenÈÄ 898 setTimeout(function() { 899 self.syncWithHorekaState('disabled', reason); 900 }, 50); 901 }; 902 } 903 904 if (typeof window.enablePlaceOrderButton === 'function') { 905 const originalEnable = window.enablePlaceOrderButton; 906 window.enablePlaceOrderButton.unifiedManagerOriginal = originalEnable; 907 908 window.enablePlaceOrderButton = function() { 909 log('[UnifiedManager] ð¨ Horeka Core cere enable butoane'); 910 911 // 1. ExecutÄ ORIGINAL Horeka Core enable logic 912 originalEnable.apply(this, arguments); 913 914 // 2. Sincronizez UnifiedManager state 915 if (self && self.state) { 916 self.state.buttonsDisabledByHoreka = false; 917 delete self.state.horekaDisableReason; 918 } 919 920 // 3. Apply UnifiedManager logic pentru toggle buttons 921 setTimeout(function() { 922 self.syncWithHorekaState('enabled'); 923 // Re-apply button toggle logic dacÄ e necesar 924 self.updateButtons(); 925 }, 50); 926 }; 927 } 928 929 // 3. PÄstreazÄ funcÈionalitatea checkPlaceOrderButtonState 930 if (typeof window.checkPlaceOrderButtonState === 'function') { 931 const originalCheck = window.checkPlaceOrderButtonState; 932 window.checkPlaceOrderButtonState.unifiedManagerOriginal = originalCheck; 933 934 // NU suprascriu aceastÄ funcÈie - o las sÄ funcÈioneze normal 935 // Horeka Core o va apela Èi va apela disable/enable care sunt deja interceptate 936 log('[UnifiedManager] â PÄstrez checkPlaceOrderButtonState original - Horeka Core va apela disable/enable interceptate'); 937 } 938 939 // 4. CheckoutButtonManager was removed in Task #011 - no longer needs deactivation 940 941 // 5. Flags pentru scripturile vechi 942 window.FAKE_CHECKOUT_TOGGLE_DISABLED = true; 943 window.BUTTON_MANAGER_DISABLED = true; 944 945 log('[UnifiedManager] â Colaborare hibridÄ configuratÄ: Horeka Core executÄ disable/enable, UnifiedManager sincronizeazÄ'); 946 }, 947 948 /** 949 * SincronizeazÄ UnifiedManager cu starea setatÄ de Horeka Core 950 */ 951 syncWithHorekaState: function(action, reason) { 952 log('[UnifiedManager] ð Sincronizez cu starea Horeka Core:', action); 953 954 const $normalButton = $(CONFIG.SELECTORS.normalButton); 955 const $fakeButton = $(CONFIG.SELECTORS.fakeButton); 956 957 if (action === 'disabled') { 958 // Horeka Core a disabled butoanele - asigur consistenÈa vizualÄ 959 960 // Adaug clase UnifiedManager pentru tracking 961 $normalButton.addClass('unified-disabled-by-horeka'); 962 $fakeButton.addClass('unified-disabled-by-horeka'); 963 964 // Asigur cÄ toggle logic nu interfereazÄ când butoanele sunt disabled 965 log('[UnifiedManager] ð Butoane disabled de Horeka Core - suspend toggle logic'); 966 967 } else if (action === 'enabled') { 968 // Horeka Core a enabled butoanele - pot continua cu toggle logic 969 970 // Elimin clase de tracking 971 $normalButton.removeClass('unified-disabled-by-horeka'); 972 $fakeButton.removeClass('unified-disabled-by-horeka'); 973 974 log('[UnifiedManager] ð Butoane enabled de Horeka Core - reiau toggle logic'); 975 } 976 }, 977 978 /** 979 * Handler pentru rezultatul validÄrii din Horeka Core 980 * AceastÄ funcÈie este apelatÄ automat când Horeka Core terminÄ validarea 981 */ 982 handleHorekaValidationResult: function(isValid) { 983 log('[UnifiedManager] ð¨ Rezultat validare primit de la Horeka Core:', isValid); 984 985 if (isValid) { 986 log('[UnifiedManager] â Validare OK - activez butoanele'); 987 this.enableButtons(); 988 } else { 989 log('[UnifiedManager] ð Validare failed - dezactivez butoanele silent (fÄrÄ popup)'); 990 this.disableButtons(); 991 } 992 }, 993 994 /** 995 * SOLUÈIE PAYMENT PERSISTENCE: SalveazÄ alegerea utilizatorului 996 * FuncÈie simplÄ care salveazÄ Ã®n localStorage Èi în state 997 * @param {string} method - Metoda de platÄ selectatÄ (ex: 'netopiapayments', 'cod') 998 */ 999 saveUserPaymentChoice: function(method) { 1000 // SalvÄm în state intern pentru acces rapid 1001 this.state.userSelectedPaymentMethod = method; 1002 1003 // SOLUÈIE: MarcheazÄ cÄ utilizatorul a fÄcut o selecÈie în aceastÄ sesiune 1004 try { 1005 sessionStorage.setItem('horeka_user_made_selection', 'true'); 1006 } catch (e) { 1007 // Ignore sessionStorage errors 1008 } 1009 1010 // SalvÄm Èi în localStorage pentru persistenÈÄ Ã®ntre sesiuni 1011 // localStorage supravieÈuieÈte Èi dupÄ refresh paginÄ 1012 try { 1013 localStorage.setItem('horeka_user_payment_method', method); 1014 log('[UnifiedManager] ð¾ Salvat în localStorage (selecÈie utilizator):', method); 1015 } catch (e) { 1016 // DacÄ localStorage nu e disponibil, folosim doar state 1017 } 1018 }, 1019 1020 /** 1021 * SOLUÈIE PAYMENT PERSISTENCE: RestaureazÄ alegerea utilizatorului 1022 * Se apeleazÄ dupÄ fiecare update_checkout pentru a pÄstra selecÈia 1023 */ 1024 restoreUserPaymentChoice: function() { 1025 log('[UnifiedManager] ð Ãncerc restaurarea metodei de platÄ (timing optimizat)...'); 1026 1027 // VERIFICARE CRITICÄ: RestaurÄm DOAR dacÄ utilizatorul a fÄcut o selecÈie anterioarÄ 1028 if (!this.state.hasUserMadeSelection) { 1029 log('[UnifiedManager] â¹ï¸ Utilizatorul nu a fÄcut încÄ o selecÈie explicitÄ - nu restaurez nimic'); 1030 return; 1031 } 1032 1033 // Prioritate 1: ÃncercÄm din state (cel mai rapid) 1034 let savedMethod = this.state.userSelectedPaymentMethod; 1035 1036 // Prioritate 2: DacÄ nu e în state, încercÄm din localStorage 1037 if (!savedMethod) { 1038 try { 1039 savedMethod = localStorage.getItem('horeka_user_payment_method'); 1040 if (savedMethod) { 1041 log('[UnifiedManager] ð MetodÄ gÄsitÄ Ã®n localStorage:', savedMethod); 1042 // DacÄ gÄsim ceva în localStorage, înseamnÄ cÄ user-ul a avut o selecÈie anterioarÄ 1043 this.state.hasUserMadeSelection = true; 1044 } 1045 } catch (e) { 1046 // localStorage indisponibil 1047 } 1048 } 1049 1050 // DacÄ nu avem nimic salvat, nu facem nimic 1051 if (!savedMethod) { 1052 log('[UnifiedManager] â¹ï¸ Nicio metodÄ salvatÄ pe
1052ntru restaurare'); 1053 return; 1054 } 1055 1056 // VerificÄm dacÄ metoda salvatÄ mai existÄ Ã®n DOM 1057 // (poate fi indisponibilÄ dupÄ update datoritÄ logicii de business) 1058 const $savedMethodElement = $('input[name="payment_method"][value="' + savedMethod + '"]'); 1059 1060 if ($savedMethodElement.length === 0) { 1061 log('[UnifiedManager] â ï¸ Metoda salvatÄ nu mai e disponibilÄ:', savedMethod); 1062 return; 1063 } 1064 1065 // SetÄm flag pentru a nu salva din nou în timpul restaurÄrii 1066 this.state.isRestoringPayment = true; 1067 1068 // RestaurÄm selecÈia 1069 log('[UnifiedManager] â Restaurez metoda de platÄ:', savedMethod); 1070 1071 // 1. SetÄm radio button-ul 1072 $savedMethodElement.prop('checked', true).trigger('change'); 1073 1074 // 2. Pentru blue cards, triggeruim Èi click pe label 1075 const $blueCard = $('.blue-cards input[value="' + savedMethod + '"]'); 1076 if ($blueCard.length) { 1077 $blueCard.prop('checked', true); 1078 $blueCard.closest('label').addClass('active'); 1079 log('[UnifiedManager] ð³ Blue card restaurat pentru:', savedMethod); 1080 } 1081 1082 // 3. Pentru dropdown (dacÄ existÄ) 1083 const $dropdown = $('select[name="payment_method"]'); 1084 if ($dropdown.length) { 1085 $dropdown.val(savedMethod); 1086 log('[UnifiedManager] ð Dropdown restaurat pentru:', savedMethod); 1087 } 1088 1089 // ActualizÄm state-ul intern 1090 this.state.currentPaymentMethod = savedMethod; 1091 1092 // ResetÄm flag-ul dupÄ un delay mic 1093 const self = this; 1094 setTimeout(function() { 1095 self.state.isRestoringPayment = false; 1096 log('[UnifiedManager] â Restaurare completÄ, gata pentru noi schimbÄri'); 1097 }, 200); 1098 }, 1099 1100 /** 1101 * Helper pentru debounce - previne apelurile multiple rapide 1102 */ 1103 debounce: function(func, delay) { 1104 let timeoutId; 1105 return function() { 1106 const context = this; 1107 const args = arguments; 1108 clearTimeout(timeoutId); 1109 timeoutId = setTimeout(function() { 1110 func.apply(context, args); 1111 }, delay); 1112 }; 1113 }, 1114 1115 /** 1116 * Helper pentru logging - reduce spam în console 1117 */ 1118 shouldLog: function() { 1119 const now = Date.now(); 1120 1121 // Log primele 3 toggle-uri 1122 if (this.state.toggleCount < 3) { 1123 return true; 1124 } 1125 1126 // DupÄ aceea, log doar la 2 secunde interval 1127 if (now - this.state.lastToggleTime > 2000) { 1128 this.state.lastToggleTime = now; 1129 return true; 1130 } 1131 1132 return false; 1133 }, 1134 1135 /** 1136 * FuncÈii pentru debugging 1137 */ 1138 debug: function() { 1139 1140 1141 } 1142 }; 1143 1144 // ======================================== 1145 // INIÈIALIZARE AUTOMATÄ 1146 // ======================================== 1147 1148 // AÈteaptÄ jQuery Èi DOM ready 1149 $(document).ready(function() { 1150 // VerificÄ dacÄ nu a fost dezactivat manual 1151 if (!window.UNIFIED_MANAGER_DISABLED) { 1152 // Delay mic pentru a permite altor scripturi sÄ se încarce 1153 setTimeout(function() { 1154 window.UnifiedButtonManager.init(); 1155 }, 100); 1156 } 1157 1158 // SOLUÈIE PAYMENT PERSISTENCE: Cleanup dupÄ plasarea comenzii 1159 // CurÄÈÄm metoda salvatÄ când suntem pe pagina de confirmare comandÄ 1160 if ($('body').hasClass('woocommerce-order-received')) { 1161 log('[UnifiedManager] ð§¹ ComandÄ plasatÄ - curÄÈ metoda de platÄ salvatÄ'); 1162 try { 1163 localStorage.removeItem('horeka_user_payment_method'); 1164 } catch (e) { 1165 // IgnorÄm erorile de localStorage 1166 } 1167 } 1168 }); 1169
1170 // Expune funcÈia de debug global 1171 window.debugUnifiedManager = function() { 1172 window.UnifiedButtonManager.debug(); 1173 }; 1174 1175 // Script încÄrcat - foloseÈte debugUnifiedManager() pentru debug 1176 1177})(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.