PageSourceSearch

https://www.cerames.it/themes/prestapremium/modules/onepagecheckoutps/views/js/front/compatibilities.js

js cerames.it collected 2026-09-30 02:41:07 UTC 103,008 bytes, 2,015 lines download raw bytes

1//Important note
2//If you want to trigger the events of the module elements, you must use
3//the exported variable window.jqueryOPC and not the jQuery of the template, for example:
4//window.jqueryOPC.find('#id_state').trigger('change');
5
6function initOpcCompatibilities() {
7    if (typeof prestashop === typeof undefined) {
8        return;
9    }
10
11    //Defensa: si el JS del tema falló antes de ejecutarse core.js (ej. SyntaxError en el
12    //bundle CCC bottom-*.js), `prestashop` queda como el objeto plano del inline del <head>
13    //y nunca se convierte en EventEmitter → `prestashop.on is not a function` rompe OPC.
14    //Polyfill mínimo on/off/once/emit para que el checkout siga operativo aunque el tema falle.
15    if (typeof prestashop.on !== 'function' || typeof prestashop.emit !== 'function') {
16        (function (ps) {
17            console.warn('[OPC] prestashop event emitter missing — theme JS likely failed (check for a SyntaxError in the theme bundle); using internal fallback');
18
19            const listeners = {};
20
21            ps.on = function (event, callback) {
22                (listeners[event] = listeners[event] || []).push(callback);
23                return ps;
24            };
25
26            ps.off = ps.removeListener = function (event, callback) {
27                if (listeners[event]) {
28                    listeners[event] = listeners[event].filter(
29                        (cb) => cb !== callback && cb._opcOnce !== callback
30                    );
31                }
32                return ps;
33            };
34
35            ps.once = function (event, callback) {
36                const wrapper = function () {
37                    ps.off(event, wrapper);
38                    callback.apply(ps, arguments);
39                };
40                wrapper._opcOnce = callback;
41                return ps.on(event, wrapper);
42            };
43
44            ps.emit = function (event) {
45                const args = Array.prototype.slice.call(arguments, 1);
46                (listeners[event] || []).slice().forEach(function (callback) {
47                    try {
48                        callback.apply(ps, args);
49                    } catch (error) {
50                        console.error('[OPC] Listener error on "' + event + '":', error);
51                    }
52                });
53                return ps;
54            };
55        })(prestashop);
56    }
57
58    // support module: colissimo - v2.2.4 - PrestaShop SA
59    // El widget de punto de recogida (widget.js) consulta puntos usando el objeto global
60    // `colissimoDeliveryAddress` (dirección de entrega, renderizada server-side desde
61    // $cart->id_address_delivery). En OPC unified el VISITANTE tiene una dirección temporal
62    // centinela en el cart (city='NA', postcode vacío) hasta confirmar, así que colissimo la
63    // emite vacía y la WS responde errorCode 104 "Code postal absent" → "Aucun point trouvé".
64    // Refrescamos `colissimoDeliveryAddress` con lo que el visitante escribió EN VIVO en el
65    // form de entrega de OPC justo antes de abrir el widget, mutando sus PROPIEDADES por
66    // nombre desnudo (funciona aunque sea `const`). Es un fallback para el caso "clic antes de
67    // que el refresh AJAX guarde/re-renderice"; el fix principal es server-side: ShippingService
68    // convierte el `const colissimoDeliveryAddress` de colissimo a `var` en cada render (SSR y
69    // AJAX) para que el refresh tras guardar la dirección actualice el objeto sin colisionar.
70    const opcColissimoIsSentinelValue = (value) => {
71        const normalized = (value || '').trim().toUpperCase();
72        return normalized === '' || normalized === 'NA' || normalized === 'NA NA' || normalized === '00000';
73    };
74
75    const opcUpdateColissimoDeliveryAddressFromForm = () => {
76        // Referencia por nombre desnudo (no window.*): en unified el shipping SSR declara
77        // `const colissimoDeliveryAddress` como binding LÉXICO global (no propiedad de window),
78        // mientras que en los refresh AJAX es `var`. El bare-name resuelve ambos por scope
79        // léxico. Mutamos sus PROPIEDADES (permitido aunque sea const), nunca reasignamos.
80        if (typeof colissimoDeliveryAddress === 'undefined'
81            || !colissimoDeliveryAddress
82            || typeof colissimoDeliveryAddress !== 'object'
83        ) {
84            return;
85        }
86        const address = colissimoDeliveryAddress;
87
88        // El wrapper del rol de entrega lleva data-type-address="delivery" en cualquier layout.
89        const $deliveryForm = $('[data-type-address="delivery"]').first();
90        if (!$deliveryForm.length) {
91            return;
92        }
93
94        const readField = (name) => {
95            const $field = $deliveryForm.find('[name="' + name + '"]').first();
96            return $field.length ? String($field.val() || '').trim() : '';
97        };
98
99        const postcode = readField('postcode');
100        const city = readField('city');
101        const address1 = readField('address1');
102
103        // Solo sobreescribir con valores reales del form; nunca degradar a centinela/vacío.
104        if (!opcColissimoIsSentinelValue(postcode)) {
105            address.zipcode = postcode;
106        }
107        if (!opcColissimoIsSentinelValue(city)) {
108            address.city = city;
109        }
110        if (!opcColissimoIsSentinelValue(address1)) {
111            address.address = address1;
112        }
113    };
114
115    // Captura (fase capture) para correr ANTES del handler delegado de colissimo, que lee
116    // `colissimoDeliveryAddress` al abrir el widget. Enlace único por request.
117    if (!window._opcColissimoAddrBound) {
118        window._opcColissimoAddrBound = true;
119        document.addEventListener('click', (event) => {
120            if (event.target && event.target.closest && event.target.closest('#colissimo-select-pickup-point')) {
121                opcUpdateColissimoDeliveryAddressFromForm();
122            }
123        }, true);
124    }
125
126    // colissimo - modal duplicado. widget.js mueve su modal (.colissimo-front-widget-17 /
127    // .colissimo-osm-front-widget) a <body> en su `ready` (que NO se re-ejecuta). OPC
128    // re-inyecta el bloque del carrier en cada refresh, dejando OTRA copia del modal en el
129    // paso de envío. Al abrir, `.modal('show')` matchea ambas → se apilan DOS .modal-backdrop
130    // que tapan el contenido y bloquean el clic (solo se cierra). Conservamos la copia que ya
131    // está en <body> (la que usa widget.js) y quitamos las duplicadas re-inyectadas.
132    const opcDedupeColissimoModals = () => {
133        ['.colissimo-front-widget-17', '.colissimo-osm-front-widget'].forEach((selector) => {
134            const $modals = $(selector);
135            if ($modals.length <= 1) {
136                return;
137            }
138
139            const $inBody = $modals.filter((_, el) => el.parentNode === document.body);
140            if ($inBody.length) {
141                $modals.not($inBody.get(0)).remove();
142            } else {
143                // Ninguna en <body>: dejar una y moverla a <body> como hace widget.js.
144                $modals.slice(0, -1).remove();
145                $modals.last().appendTo('body');
146            }
147        });
148    };
149
150    let intervalPaymentToggleOrderButtonValidation = null;
151
152    //uso de la funcion compareVersion:
153    //console.log(compareVersion('1.0.0', '1.0.1')); // -1 (v1 < v2)
154    //console.log(compareVersion('1.0.2', '1.0.1')); // 1 (v1 > v2)
155    //console.log(compareVersion('1.0.2', '1.0.2')); // 0 (v1 == v2)
156    const compareVersion = (v1, v2) => {
157        if (typeof v1 !== 'string') return false;
158        if (typeof v2 !== 'string') return false;
159        v1 = v1.split('.');
160        v2 = v2.split('.');
161        const k = Math.min(v1.length, v2.length);
162        for (let i = 0; i < k; ++i) {
163            v1[i] = parseInt(v1[i], 10);
164            v2[i] = parseInt(v2[i], 10);
165            if (v1[i] > v2[i]) return 1;
166            if (v1[i] < v2[i]) return -1;
167        }
168        return v1.length === v2.length ? 0 : v1.length < v2.length ? -1 : 1;
169    };
170
171    // App - Events
172    prestashop.on('opc-app-init', (params) => {
173        const { JqueryElementMain } = params;
174
175        $(document).on('ajaxSend', (e, jqXHR, settings) => {
176            if (settings.url === window.prestashop.urls.pages.order
177                && settings.data
178                && settings.data.includes('action=checkCartStillOrderable')
179            ) {
180                $('#opc-checkout-finalize').prop('disabled', true);
181            }
182        });
183
184        $(document).on('ajaxComplete', (e, jqXHR, settings) => {
185            if (settings.url === window.prestashop.urls.pages.order
186                && settings.data
187                && settings.data.includes('action=checkCartStillOrderable')
188            ) {
189                $('#opc-checkout-finalize').prop('disabled', false);
190            }
191
192        });
193
194        //support module: mondialrelay - v4.1.2/v4.1.4/v4.2.0 - ScaleDEV / sendcloudv2 - v2.0.24 - Sendcloud
195        //Ambos módulos persisten el punto de recogida vía XHR propio tras seleccionarlo:
196        // - mondialrelay → action=saveSelectedRelay (v4.1.2 XHR nativo; v4.1.4+ $.ajax al click del botón único; v4.2.0 sin cambios en el front de checkout)
197        // - sendcloudv2 → controller ServicePointSelection (responde 204 No Content)
198        //OPC no detecta esos XHR con ajaxComplete, por lo que el paso de pagos no se recarga
199        //y el cliente no puede continuar la compra.
200        //Interceptamos XHR y forzamos la recarga de pagos al completar la llamada.
201        //IMPORTANTE: la evaluación se hace en el evento `load` (no en send) usando
202        //`this.responseURL` como fallback de URL: scripts dinámicos del widget de MR
203        //pueden re-sobrescribir XMLHttpRequest.prototype.open SIN encadenar, perdiendo
204        //la URL capturada (`_opcPickupPointUrl`) y rompiendo el match (visto con v4.1.4).
205        //Gate mondialrelay: la jsDef MONDIALRELAY_AJAX_CHECKOUT_URL viene inline en el HTML
206        //inicial (Media::addJsDef) siempre que el módulo está activo. NO usar solo el tag
207        //script: el del widget CDN se inyecta dinámicamente al inicializar el widget (no
208        //existe aún en la 1ª carga sin carrier MR elegido) y el JS local del módulo puede
209        //ir combinado por CCC en bottom-*.js (src sin "mondialrelay").
210        const hasMondialRelay = typeof window.MONDIALRELAY_AJAX_CHECKOUT_URL !== 'undefined'
211            || document.querySelector('script[src*="mondialrelay"]') !== null;
212        const hasSendcloudV2 = document.querySelector('script[src*="servicepoints.sendcloud.sc/shops/prestashop_v2"]') !== null;
213        if (hasMondialRelay || hasSendcloudV2) {
214            (function () {
215                const _xhrOpen = XMLHttpRequest.prototype.open;
216                const _xhrSend = XMLHttpRequest.prototype.send;
217
218                XMLHttpRequest.prototype.open = function (method, url) {
219                    this._opcPickupPointUrl = typeof url === 'string' ? url : null;
220                    return _xhrOpen.apply(this, arguments);
221                };
222
223                XMLHttpRequest.prototype.send = function (body) {
224                    const bodyStr = typeof body === 'string' ? body : '';
225
226                    this.addEventListener('load', function () {
227                        const url = this._opcPickupPointUrl || this.responseURL || '';
228                        //mondialrelay: si la URL no sobrevivió (open pisado por otro script),
229                        //el body urlencoded `action=saveSelectedRelay` es suficientemente único.
230                        const isMondialRelay = bodyStr.includes('saveSelectedRelay')
231                            && (url === '' || url.includes('mondialrelay'));
232                        const isSendcloudV2 = url.includes('sendcloudv2')
233                            && url.includes('ServicePointSelection');
234
235                        if (!isMondialRelay && !isSendcloudV2) {
236                            return;
237                        }
238
239                        //Debounce: módulos con listeners duplicados disparan múltiples XHR
240                        //en ráfaga (ej. sendcloudv2 tras re-renders). Emitimos 
240solo una vez.
241                        if (window._opcPickupPointEmitTimer) {
242                            clearTimeout(window._opcPickupPointEmitTimer);
243                        }
244                        window._opcPickupPointEmitTimer = setTimeout(function () {
245                            prestashop.emit('opc-payment-getPaymentList', {});
246                            window._opcPickupPointEmitTimer = null;
247                        }, 300);
248                    });
249
250                    return _xhrSend.apply(this, arguments);
251                };
252            })();
253        }
254    });
255
256    // Field - Events
257    prestashop.on('opc-field-save-complete', (params) => {
258        const { JqueryElementForm } = params;
259    });
260
261    // Login - Events
262    prestashop.on('opc-login_customer-loginCustomer-beforeSend', (params) => {
263        const { JqueryElementMain, JqueryElement } = params;
264    });
265
266    prestashop.on('opc-login-loginCustomer-error', (params) => {
267        const { JqueryElement } = params;
268
269        // idxrdefender - v1.3.2 - innovadeluxe
270        if (typeof IdxrdefenderFront !== typeof undefined) {
271            JqueryElement.find('#g-recaptcha-response-login').val('');
272            let defender = new IdxrdefenderFront();
273            defender.bindsCaptcha();
274        }
275    });
276
277    // Personal Information - Events
278    prestashop.on('opc-personal_information-saveCustomer-beforeSend', (params) => {
279        const { JqueryElementMain, JqueryElement } = params;
280    });
281
282    prestashop.on('opc-personal_information-saveCustomer-error', (params) => {
283        const { JqueryElement } = params;
284
285        // idxrdefender - v1.3.2 - innovadeluxe
286        if (typeof IdxrdefenderFront !== typeof undefined) {
287            JqueryElement.find('#g-recaptcha-response-login').val('');
288            let defender = new IdxrdefenderFront();
289            defender.bindsCaptcha();
290        }
291    });
292
293    // Addresses - Events
294    prestashop.on('opc-addresses-initEventFormAddress', (params) => {
295        const { formAddress } = params;
296
297        // chilexpress_oficial - v1.2.9 - Chilexpress
298        if (typeof chilexpress_oficial_ajax_url !== typeof undefined) {
299            const typeAddress = formAddress.data('component');
300            let countrIsoCode = OPC.Addresses.country.iso_code;
301
302            const $countrySelector = formAddress.find('[name=id_country]');
303            if ($countrySelector.length > 0) {
304                countrIsoCode = $countrySelector.find('option:selected').data('iso-code');
305            }
306
307            if (countrIsoCode == 'CL') {
308                const $stateSelector = formAddress.find('[name=id_state]');
309
310                $stateSelector.addClass('reloadAddresses');
311
312                const $citySelector = formAddress.find('[name=city]');
313                let $cityListSelector = formAddress.find('[name=city_list]');
314
315                $citySelector.off('blur');
316
317                $.ajax({
318                    type: 'POST',
319                    url: chilexpress_oficial_ajax_url.replace(/&amp;/gi,'&'),
320                    cache: false,
321                    dataType: 'json',
322                    data: {
323                        action : 'obtener_comunas_con_id',
324                        region : $stateSelector.val(),
325                        ajax: true
326                    },
327                    success: function (result) {
328                        if (typeof result.comunas !== undefined) {
329                            if ($cityListSelector.length <= 0) {
330                                $cityListSelector = $('<select/>')
331                                    .attr({
332                                        id: typeAddress + '_city_list',
333                                        class: 'form-control input-sm ' + typeAddress
334                                    })
335                                    .on('change', function(event) {
336                                        const optionSelected = $(event.currentTarget).find('option:selected');
337
338                                        $citySelector.val(
339                                            $(optionSelected).attr('value')
340                                        ).trigger('blur');
341                                    }
342                                );
343                            } else {
344                                $cityListSelector.empty().hide();
345                            }
346
347                            $('<option/>')
348                                .attr({
349                                    value: ''
350                                })
351                                .append('--')
352                                .appendTo($cityListSelector);
353
354                            $.each(result.comunas, function(key, city) {
355                                $('<option/>')
356                                    .attr({
357                                        'value': key
358                                    })
359                                    .append(city)
360                                    .appendTo($cityListSelector);
361                            });
362
363                            $citySelector.parents('.form-group')
364                                .append($cityListSelector);
365
366                            if ($cityListSelector.find('option[value="' + $citySelector.val() + '"]').length > 0) {
367                                $cityListSelector.val($citySelector.val());
368                            } else {
369                                $citySelector.val('');
370                            }
371
372                             $cityListSelector.show();
373                             $citySelector.hide();
374                        }
375
376                        // if (typeof result.comunas !== undefined) {
377                        //     $citySelector.typeahead({
378                        //         highlight: true,
379                        //         items : 'all',
380                        //         showHintOnFocus: false,
381                        //         autoSelect: true,
382                        //         minLength: 0,
383                        //         source: function (query, process) {
384                        //             process($.map(result.comunas, function (city) {
385                        //                 return {
386                        //                     name: city,
387                        //                     value: city
388                        //                 };
389                        //             }));
390                        //         },
391                        //         updater: function (item) {
392                        //             //$citySelector.val(item.name);
393
394                        //             return item.value;
395                        //         }
396                        //     });
397                        // }
398                    },
399                });
400            }
401        }
402    });
403    prestashop.on('opc-addresses-getAddresses-complete', (params) => {
404        const { JqueryElementMain, JqueryElement } = params;
405
406        //facturare - v4.1.5 - Daniel Patilea
407        if (typeof initListen !== typeof undefined) {
408            initListen();
409        }
410    });
411    prestashop.on('opc-addresses-getAddresses-complete-edit', (params) => {
412        const { JqueryElementMain, JqueryElement } = params;
413    });
414    prestashop.on('opc-addresses-getAddresses-complete-add', (params) => {
415        const { JqueryElementMain, JqueryElement } = params;
416    });
417
418    // Shipping - Events
419    prestashop.on('opc-shipping-getCarrierList-complete', (params) => {
420        const { JqueryElement, JqueryElementContent } = params;
421
422        // colissimo - v2.2.4 - deja la dirección de entrega del widget alineada con el form
423        // en vivo tras cada refresh (cubre el modo OSM auto-abierto, que no espera al click)
424        // y elimina los modales duplicados que re-inyecta OPC (evita doble backdrop).
425        opcUpdateColissimoDeliveryAddressFromForm();
426        opcDedupeColissimoModals();
427
428        // econtdelivery - v2.0.3 - ITPartner
429        // Econt usa un iframe externo que envía postMessage con el precio de envío tras seleccionar
430        // un punto de entrega. El JS inline original del módulo se ejecutó en DOMContentLoaded y no
431        // se re-ejecuta cuando OPC re-renderiza los carriers vía AJAX. Registramos el listener de
432        // postMessage aquí (cuando el iframe ya existe en el DOM) para:
433        // 1) Guardar la info de envío vía AJAX (mismo endpoint que usa Econt)
434        // 2) Disparar updateCarrier para que OPC re-lea getOrderShippingCost (ahora con dato en BD)
435        // 3) Marcar que el punto de entrega fue seleccionado (para validación del botón de pago)
436        if (JqueryElement.find('#econt_delivery_customer_info_frame').length > 0) {
437            // Solo inicializar el flag si no está definido (evita resetearlo tras updateCarrier)
438            if (typeof window._opcEcontDeliverySelected === 'undefined') {
439                window._opcEcontDeliverySelected = false;
440            }
441
442            $(window).off('message.opcEcontShipping').on('message.opcEcontShipping', function (e) {
443                const data = e.originalEvent.data;
444
445                if (!data || typeof data !== 'object' || !data.shipping_price) {
446                    return;
447                }
448
449                // Guardar info de envío en BD via actionDispatcherBefore del módulo Econt.
450                // El endpoint /module/econtdelivery/ajax solo guarda en SESSION/cookie,
451                // pero getOrderShippingCost() lee de la tabla econt_delivery_cart_info (BD).
452                // Enviamos ajax_econt_save + econt_data a cualquier URL de PS para que
453                // hookActionDispatcherBefore lo intercepte y guarde en BD.
454                $.ajax({
455                    url: prestashop.urls.pages.order,
456                    type: 'POST',
457                    data: {
458                        ajax: 1,
459                        ajax_econt_save: 1,
460                        econt_data: JSON.stringify(data)
461                    },
462                    dataType: 'json',
463                    complete: function () {
464                        window._opcEcontDeliverySelected = true;
465
466                        // Actualizar el hidden input del módulo Econt
467                        $('#econt_delivery_info').val(JSON.stringify(data));
468
469                        // Forzar a OPC a refrescar el carrier (re-lee precio de BD)
470                        prestashop.emit('opc-cart-updateCarrier');
471                    }
472                });
473            });
474        }
475
476        // boxnow - v3.1.0 - BoxNow
477        // El script inline del módulo (boxnow.tpl) solo carga el widget real del mapa
478        // (widget-cdn.boxnow.gr/map-widget/client/v5.js) dentro de un listener
479        // `window.addEventListener('load', ...)`. Ese evento del navegador dispara UNA
480        // sola vez, en la carga inicial de la página; OPC inyecta el bloque de shipping
481        // (y por tanto este script) siempre vía AJAX ($shippingContainer.html(...), incluso
482        // en el primer render), así que cuando se ejecuta el `load` ya pasó hace rato y el
483        // callback nunca corre → v5.js (el que realmente pinta el mapa) nunca se solicita y
484        // el contenedor queda vacío. Mismo patrón que gelproximity/advanceddateofdelivery/
485        // shaim_dpdparcelshop más abajo: recargamos el script del widget manualmente en cada
486        // refresh para que se monte sobre el contenedor recién inyectado.
487        if (JqueryElement.find('#boxnow-map-container').length > 0 && typeof window._bn_map_widget_config !== typeof undefined) {
488            let _script = document.createElement('script');
489            _script.type = 'text/javascript';
490            _script.src = 'https://widget-cdn.boxnow.gr/map-widget/client/v5.js';
491            JqueryElementContent.append(_script);
492        }
493
494        //support module: mondialrelay - v3.3.3 - ScaleDEV
495        //se hizo modificacion a medida en dicho modulo para poder funcionar.
496        if (typeof window.mondialrelayWidget !== typeof undefined) {
497            if (typeof MONDIALRELAY_SELECTED_RELAY_IDENTIFIER !== typeof undefined) {
498                window.mondialrelayWidget.savedRelay = MONDIALRELAY_SELECTED_RELAY_IDENTIFIER;
499            }
500
501            $(window.mondialrelayWidget).trigger('mondialrelay.ready');
502
503            // if (typeof window.mondialrelayWidget !== typeof undefined) {
504            //     $(document).off('click', window.mondialrelayWidget.save_button);
505            // }
506
507            // let _script = document.createElement('script');
508            // _script.type = 'text/javascript';
509            // _script.src = prestashop.urls.base_url + 'modules/mondialrelay/views/js/front/checkout/checkout-17.js';
510            // JqueryElementContent.append(_script);
511
512            // if (typeof window.mondialrelayWidget !== typeof undefined) {
513            //     if (typeof MONDIALRELAY_SELECTED_RELAY_IDENTIFIER !== typeof undefined) {
514            //         window.mondialrelayWidget.savedRelay = MONDIALRELAY_SELECTED_RELAY_IDENTIFIER;
515            //     }
516
517            //     $(window.mondialrelayWidget).trigger('mondialrelay.ready');
518            // }
519        }
520
521        //gelproximity - v1.2.9 - iModuli.it
522        if (typeof gelprx_conf !== typeof undefined) {
523            let _script = document.createElement('script');
524            _script.type = 'text/javascript';
525            _script.src = prestashop.urls.base_url + 'modules/gelproximity/views/js/8/front.js';
526            JqueryElementContent.append(_script);
527        }
528
529        /* Smartpost Shipping - v1.2.13 - Mijora.lt */
530        if (typeof ItellaModule !== typeof undefined) {
531            ItellaModule.init();
532        }
533
534        /* Venipak Shipping - v1.1.5 - mijora.lt */
535        if (typeof venipak_custom_modal !== typeof undefined) {
536            venipak_custom_modal();
537        }
538
539        //swastarkencl - v3.6.0 - Softwareagil
540        if (typeof swastarkenclRunOnReady !== typeof undefined) {
541            swastarkenclRunOnReady();
542        }
543
544        /* advanceddateofdelivery - v3.1.0 - Créatym */
545        if (typeof cartDatesDelivery !== typeof undefined) {
546            JqueryElementContent.find('.delivery-options').after($('#advanceddateofdelivery'));
547
548            let _script = document.createElement('script');
549            _script.type = 'text/javascript';
550            _script.src = prestashop.urls.base_url + 'modules/advanceddateofdelivery/views/js/cart.js';
551            JqueryElement.append(_script);
552        }
553
554        /* shaim_dpdparcelshop - v2.3.10 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
555        if (typeof updateCarrierExtraContentDpdparcelshop !== typeof undefined) {
556            let _script = document.createElement('script');
557            _script.type = 'text/javascript';
558            _script.src = prestashop.urls.base_url + '/modules/shaim_dpdparcelshop/shaim_dpdparcelshop.js';
559            JqueryElement.append(_script);
560
561            updateCarrierExtraContentDpdparcelshop(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
562
563            $('.psmoduly_balikovy_modul').on('show.bs.modal', (e) => {
564                $(e.currentTarget).find('.close').attr('data-bs-dismiss', 'modal');
565            });
566        }
567
568        /* shaim_zasilkovna_widget - v2.3.7 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
569        if (typeof updateCarrierExtraContentZasilkovnaWidget !== typeof undefined) {
570            let _script = document.createElement('script');
571            _script.type = 'text/javascript';
572            _script.src = prestashop.urls.base_url + '/modules/shaim_zasilkovna_widget/shaim_zasilkovna_widget.js';
573            JqueryElement.append(_script);
574
575            LoadNowZasilkovnaWidget();
576
577            updateCarrierExtraContentZasilkovnaWidget(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
578        }
579
580        /* shaim_baliknapostu - v2.3.7 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
581        if (typeof updateCarrierExtraContentBalikNaPostu !== typeof undefined) {
582            let _script = document.createElement('script');
583            _script.type = 'text/javascript';
584            _script.src = prestashop.urls.base_url + '/modules/shaim_baliknapostu/shaim_baliknapostu.js';
585            JqueryElement.append(_script);
586
587            LoadNowBaliknapostu();
588
589            updateCarrierExtraContentBalikNaPostu(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
590        }
591
592        /* shaim_balikovna - v2.3.7 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
593        if (typeof updateCarrierExtraContentBalikovna !== typeof undefined) {
594            let _script = document.createElement('script');
595            _script.type = 'text/javascript';
596            _script.src = prestashop.urls.base_url + '/modules/shaim_balikovna/shaim_balikovna.js';
597            JqueryElement.append(_script);
598
599            LoadNowBalikovna();
600
601            updateCarrierExtraContentBalikovna(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
602        }
603
604        /* shaim_glsparcelshop - v2.3.7 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
605        if (typeof updateCarrierExtraContentGLSParcelShop !== typeof undefined) {
606            let _script = document.createElement('script');
607            _script.type = 'text/javascript';
608            _script.src = prestashop.urls.base_url + '/modules/shaim_glsparcelshop/shaim_glsparcelshop.js';
609            JqueryElement.append(_script);
610
611            LoadNowGlsparcelshop();
612
613            updateCarrierExtraContentGlsparcelshop(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
614        }
615
616        /* shaim_pplparcelshop - v2.3.7 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
617        if (typeof updateCarrierExtraContentPplparcelshop !== typeof undefined) {
618            let _script = document.createElement('script');
619            _script.type = 'text/javascript';
620            _script.src = prestashop.urls.base_url + '/modules/shaim_pplparcelshop/shaim_pplparcelshop.js';
621            JqueryElement.append(_script);
622
623            LoadNowPplparcelshop();
624
625            updateCarrierExtraContentPplparcelshop(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
626        }
627
628        /* shaim_intime - v2.3.7 - Dominik Shaim Ulrich (www.psmoduly.cz / www.openservis.cz) */
629        if (typeof updateCarrierExtraContentIntime !== typeof undefined) {
630            let _script = document.createElement('script');
631            _script.type = 'text/javascript';
632            _script.src = prestashop.urls.base_url + '/modules/shaim_intime/shaim_intime.js';
633            JqueryElement.append(_script);
634
635            LoadNowIntime();
636
637            updateCarrierExtraContentIntime(parseInt(JqueryElement.find(".delivery_option_radio:checked").val()));
638        }
639
640        //gmparcellocker - v1.4.1 - GreenMouseStudio.com
641        if (typeof gmParcelLockerAjaxUrl !== typeof undefined && JqueryElement.find('#easypack-widget').length > 0) {
642            window.easyPack.dropdownWidget('easypack-widget', function (point) {
643                const pointData = point.name + '| ' + point.address.line1 + '| ' + point.address.line2;
644                $.ajax({
645                    url: gmParcelLockerAjaxUrl,
646                    type: 'POST',
647                    crossDomain: true,
648                    data: {cartId: gmCartId, pointData: pointData},
649                    async: true,
650                    dataType: "json",
651                    headers: {"cache-control": "no-cache"},
652                    success: function (data) {
653                        if (data.msg == 'OK') {
654                            $('.chosen-parcel').html(pointData.split('|').join(','));
655
656                            if (typeof window.checkGmParcellocker !== typeof undefined) {
657                                window.gmparcellockerCheckStoreSelected(JqueryElement);
658                            }
659                        }
660                    },
661                    error: function (jqXHR, textStatus) {
662                        console.log(jqXHR.responseText);
663                    }
664                });
665            });
666
667            window.gmparcellockerCheckStoreSelected = function(JqueryElement) {
668                if (JqueryElement.find('.gmparcellocker-button').is(':visible')) {
669                    window.checkGmParcellocker = true;
670                    if (JqueryElement.find('.chosen-parcel').text().length > 3) {
671                        window.gmparcellockerStoreSelected = true;
672                    } else {
673                        window.gmparcellockerStoreSelected = false;
674                    }
675                } else {
676                    if (window.checkGmParcellocker) {
677                        window.gmparcellockerStoreSelected = true;
678                        window.checkGmParcellocker = false;
679                    }
680                }
681            }
682
683            const gmparcellocker = window.setInterval(function () {
684                if (typeof window.checkGmParcellocker !== typeof undefined) {
685                    window.gmparcellockerCheckStoreSelected(JqueryElement);
686
687                    clearInterval(gmparcellocker);
688                }
689            }, 500);
690        }
691
692        //support module: sendcloud - v1.5.2 - Sendcloud _ sendcloudv2 - v2.0.24 - Sendcloud
693        const scriptSendCloud = $('script[src^="https://servicepoints.sendcloud.sc/shops/prestashop/"]').attr('src');
694        const scriptSendCloudV2 = $('script[src^="https://servicepoints.sendcloud.sc/shops/prestashop_v2/"]').attr('src');
695        if (typeof scriptSendCloud !== typeof undefined || typeof scriptSendCloudV2 !== typeof undefined) {
696            let _script = document.createElement('script');
697                _script.type = 'text/javascript';
698                _script.src = scriptSendCloud || scriptSendCloudV2;
699                JqueryElementContent.append(_script);
700        }
701
702        // correosoficial - v1.2.8.0 - Grupo Correos
703        if ($('.carrier-extra-content .correos_oficial').length > 0) {
704            let _script = document.createElement('script');
705            _script.type = 'text/javascript';
706            _script.src = prestashop.urls.base_url + '/modules/correosoficial/js/checkout.js';
707            JqueryElement.append(_script);
708        }
709
710        //support module: seur - v2.5.9 - de Seur
711        if (typeof initSeurCarriers !== typeof undefined) {
712            initSeurCarriers();
713        }
714
715        // payseradelivery - v1.5.2- Paysera
716        if ($('.paysera-delivery-gateway-content').length > 0) {
717            let _script = document.createElement('script');
718            _script.type = 'text/javascript';
719            _script.src = prestashop.urls.base_url + '/modules/payseradelivery/views/js/frontend/terminal_selection.js';
720            JqueryElement.append(_script);
721        }
722
723        // darique - v3.0.10 - Silbersaiten
724        if (typeof dariqueModule !== typeof undefined) {
725            dariqueModule.initLoaderBlock();
726            dariqueModule.loadPresentsBlock(0, []);
727        }
728
729        /* chronopost - v7.6.4 - Chronopost Official */
730        if (typeof cleanContainers !== 'undefined' && typeof toggleRelaisMap !== 'undefined') {
731            // Reset stale Leaflet state (OPC AJAX rebuilds the DOM, relais_map queda huérfano)
732            if (typeof relais_map !== 'undefined' && relais_map !== null) {
733                try { relais_map.remove(); } catch (e) {}
734                relais_map = null;
735            }
736            if (typeof marker_group !== 'undefined') marker_group = null;
737            if (typeof map_markers !== 'undefined') map_markers = {};
738            // Reset interaction guards (evita que initRelaisMap() quede bloqueado)
739            if (typeof lastMapInteractionTime !== 'undefined') lastMapInteractionTime = 0;
740            if (typeof userIsInteractingWithMap !== 'undefined') userIsInteractingWithMap = false;
741            if (typeof userJustSelectedRelayPoint !== 'undefined') userJustSelectedRelayPoint = false;
742
743            // cleanContainers() N veces: cada dummy extra genera un contenedor duplicado
744            const chronoDummies = document.querySelectorAll('[id="chronorelais_dummy_container"]').length;
745            for (let i = 0; i < Math.max(chronoDummies, 1); i++) {
746                cleanContainers();
747            }
748            toggleRelaisMap(
749                typeof cust_address_clean !== 'undefined' ? cust_address_clean : '',
750                typeof cust_codePostal !== 'undefined' ? cust_codePostal : '',
751                typeof cust_city !== 'undefined' ? cust_city : ''
752            );
753
754            // Fallback RELAISEUROPE_ID=-1: toggleRelaisMap oculta el contenedor, forzamos show
755            const $chronoContainer = $('#chronorelais_container');
756            if ($chronoContainer.length && !$chronoContainer.is(':visible')) {
757                const isInsideSelectedOption = $chronoContainer.closest('.delivery-option-selected').length > 0
758                    || $chronoContainer.parent('.carrier-extra-content').prev('.delivery-option-selected').length > 0;
759
760                if (isInsideSelectedOption && typeof initRelaisMap !== 'undefined') {
761                    $chronoContainer.show();
762                    $('#chrono_postcode_controls').show();
763                    initRelaisMap(
764                        typeof cust_address_clean !== 'undefined' ? cust_address_clean : null,
765                        typeof cust_codePostal !== 'undefined' ? cust_codePostal : null,
766                        typeof cust_city !== 'undefined' ? cust_city : null,
767                        typeof cust_country !== 'undefined' ? cust_country : null
768                    );
769                }
770            }
771
772            // OPC fix: Leaflet cachea el tamaño del contenedor al inicializarse. Como
773            // OPC inyecta el HTML del carrier por AJAX y cleanContainers() mueve
774            // #chronorelais_container al delivery-option activo, el mapa se crea antes
775            // de que el contenedor tenga su ancho final → queda "pequeño" (tiles solo
776            // en una esquina) hasta que un resize de ventana fuerza invalidateSize().
777            // El setTimeout(invalidateSize, 200) propio de chronopost corre una sola
778            // vez y a menudo antes de que el layout se estabilice. Un ResizeObserver
779            // reproduce el efecto del resize manual: cada vez que #chronorelais_map
780            // cambia de tamaño (0→500, reflow de columna, expand del delivery-option)
781            // reejecuta invalidateSize() sobre el mapa vigente.
782            const chronoMapEl = document.getElementById('chronorelais_map');
783            if (chronoMapEl && typeof ResizeObserver !== 'undefined') {
784                if (window._opcChronoMapRO) {
785                    try { window._opcChronoMapRO.disconnect(); } catch (e) {}
786                }
787                let chronoMapRaf = null;
788                window._opcChronoMapRO = new ResizeObserver(function () {
789                    if (chronoMapRaf) cancelAnimationFrame(chronoMapRaf);
790                    chronoMapRaf = requestAnimationFrame(function () {
791                        if (typeof relais_map !== 'undefined' && relais_map
792                            && typeof relais_map.invalidateSize === 'function') {
793                            relais_map.invalidateSize({ animate: false });
794                        }
795                    });
796                });
797                window._opcChronoMapRO.observe(chronoMapEl);
798            }
799
800            // Handler directo en #change_postcode: dispara antes que el delegado de body,
801            // resetea guards y evita doble AJAX via stopPropagation
802            const $changePostcodeBtn = $('#change_postcode');
803            if ($changePostcodeBtn.length && typeof initRelaisMap !== 'undefined') {
804                $changePostcodeBtn.off('click.opc').on('click.opc', function (e) {
805                    e.stopPropagation();
806                    e.preventDefault();
807                    if (typeof lastMapInteractionTime !== 'undefined') lastMapInteractionTime = 0;
808                    if (typeof userIsInteractingWithMap !== 'undefined') userIsInteractingWithMap = false;
809                    if (typeof userJustSelectedRelayPoint !== 'undefined') userJustSelectedRelayPoint = false;
810                    const newPostcode = $('#relais_postcode').val();
811                    if (!newPostcode) return;
812                    initRelaisMap(null, newPostcode, null, typeof cust_country !== 'undefined' ? cust_country : null);
813                });
814            }
815        }
816
817        // dpdfrance - v6.5.1 - DPD France S.A.S
818        const _dpdFranceDisplay = typeof dpdFranceDisplayMethodBlock !== 'undefined'
819            ? dpdFranceDisplayMethodBlock
820            : (typeof dpdfrance_display !== 'undefined' ? dpdfrance_display : null);
821        if (_dpdFranceDisplay) {
822            _dpdFranceDisplay();
823            // OPC fix: los métodos de display de dpdfrance intentan appendTo('.delivery-option > label')
824            // que no existe en OPC. Si algún bloque .dpdfrance_fo sigue atrapado en el contenedor
825            // oculto (#hook-hookDisplayAfterCarrier), lo movemos al .delivery-option activo.
826            const $activeOption = $('[id^=delivery_option]:checked').closest('.delivery-option');
827            $('#hook-hookDisplayAfterCarrier .dpdfrance_fo').each(function () {
828                $activeOption.append($(this));
829            });
830
831            // OPC fix: dpdfrance vincula su listener de click en DOMContentLoaded sobre los elementos
832            // de tabla originales. Cuando OPC re-renderiza la sección de carriers esos elementos son
833            // destruidos y los listeners se pierden. Re-vinculamos con delegación en document para que
834            // sobreviva a futuros re-renders (namespace .dpdfrance-opc evita duplicados).
835            $(document).off('click.dpdfrance-opc').on(
836                'click.dpdfrance-opc',
837                '#dpdfrance_retail_point_table tr.dpdfrance_lignepr,' +
838                '#dpdfrance_locker_point_table tr.dpdfrance_lignepr,' +
839                '#dpdfrance_relais_point_table tr.dpdfrance_lignepr',
840                function (event) {
841                    event.preventDefault();
842                    const pudoId = $(this).data('relayId');
843                    if (typeof dpdFranceRegisterPudo !== 'undefined') {
844                        dpdFranceRegisterPudo(pudoId);
845                    } else if (typeof dpdfrance_registerPudo !== 'undefined') {
846                        dpdfrance_registerPudo(pudoId);
847                    }
848                }
849            );
850        }
851    });
852
853    // Payment - Events
854    prestashop.on('opc-payment-getPaymentList-complete', (params) => {
855        const { JqueryElement } = params;
856
857        let paymentList = {};
858        JqueryElement.find('.payment_radio').each(function (i, item) {
859            paymentList[$(item).val()] = $(item).data('module-version');
860        });
861
862        // Deferred Checkout — pre-deferred (visitante en layout unified, sin `?ck=`):
863        // los módulos en `paymentListNeedDeferredPage` se muestran solo como preview
864        // (sólo título + logo, ver `payment/_partials/list.tpl`). Excluirlos aquí
865        // evita que las compatibilidades de cada módulo (Stripe, Redsys, PayPal,
866        // Mollie…) carguen sus SDKs/iframes e instalen listeners que mostrarían el
867        // form al hacer click en el radio. Tras Buy Now, la página deferred se
868        // recarga con `?ck=` activo y `OPC.Checkout.deferredPayment.active=true`,
869        // por lo que este filtro se desactiva y el SDK se monta normalmente.
870        (function () {
871            const opcConfig = window.OPC || {};
872            const deferredList = (opcConfig.Payment
873                && opcConfig.Payment.paymentListNeedDeferredPage) || [];
874            if (deferredList.length === 0) {
875                return;
876            }
877            const isUnified = !!(opcConfig.General && opcConfig.General.isUnifiedLayout);
878            const isDeferredPage = !!(opcConfig.Checkout
879                && opcConfig.Checkout.deferredPayment
880                && opcConfig.Checkout.deferredPayment.active);
881            const isVisitor = !(opcConfig.General && opcConfig.General.isLogged);
882            if (!isVisitor || !isUnified || isDeferredPage) {
883                return;
884            }
885            deferredList.forEach((moduleName) => {
886                delete paymentList[moduleName];
887            });
888        })();
889
890        // prevents rage clicking on submit button and related issues
891        const forms = JqueryElement.find('form');
892        forms.each(function() {
893            const $form = $(this);
894            const $submitButton = $form.find('button[type="submit"]');
895
896            $form.on('submit', function(e) {
897                if ($form.data('submitting')) {
898                    e.preventDefault();
899                    return false;
900                }
901
902                $form.data('submitting', true);
903                $submitButton.prop('disabled', true).addClass('disabled');
904
905                // Re-enable form after 5 seconds in case of error
906                setTimeout(function() {
907                    $form.data('submitting', false);
908                    $submitButton.prop('disabled', false).removeClass('disabled');
909                }, 5000);
910            });
911        });
912
913        /* cryptopaymentslight - v1.0.3 - UNAS Holding */
914        if (typeof initCryptoPaymentsLightUI === 'function') {
915            initCryptoPaymentsLightUI();
916        }
917
918        /* wkauthorizepayment - v1.0.1 - Webkul */
919        if ($('#wkauthorize_pay').length > 0) {
920            $('#payment-confirmation > .ps-shown-by-js > button').click(function(e) {
921                e.preventDefault();
922                var is_authorize_slected = $('.payment-options').find("input[data-module-name='wkauthorizepayment']").is(':checked');
923                if (is_authorize_slected) {
924                    $('#wkauthorize_pay').click();
925                    return false;
926                }
927            });
928        }
929
930        //ps_checkout - v2.15.4+ - PrestaShop
931        // En layouts unified pre-deferred + visitante (carrito sin cliente/dirección
932        // reales), `payment.tpl` suprime los hooks `displayPaymentTop` y
933        // `displayPaymentByBinaries`. Sin secciones `.js-payment-ps_checkout` no hay
934        // nada que renderizar — saltamos para no ocultar las radios de preview ni
935        // disparar `renderCheckout()` sobre un DOM vacío. El SDK arrancará con cart
936        // completo en la página deferred.
937        if (typeof window.ps_checkout !== typeof undefined && typeof JqueryElement !== typeof undefined
938            && JqueryElement.find('.js-payment-ps_checkout').length > 0
939        ) {
940            // Sólo crear el bus de eventos si el SDK aún no lo expuso. Sobrescribirlo
941            // tras la inicialización del SDK perdería la referencia interna que el SDK
942            // usa para emitir `payment-option-active`, y los contenedores ocultos
943            // abajo nunca se volverían a mostrar.
944            if (!window.ps_checkout.events) {
945                window.ps_checkout.events = new EventTarget();
946            }
947            window.ps_checkout.events.addEventListener('payment-option-active', ({ detail }) => {
948                const { HTMLElementContainer } = detail;
949                $(HTMLElementContainer).parent().show();
950            });
951
952            JqueryElement.find('.payment_radio').each(function (i, item) {
953                const $item = $(item);
954                const moduleName = $item.data('module-name');
955                const $container = $item.parents('#' + $item.prop('id') + '-container');
956
957                if (-1 !== moduleName.search('ps_checkout')) {
958                    $container.parent().hide();
959                }
960            });
961
962            if (typeof window.ps_checkout.renderCheckout === 'function') {
963                window.ps_checkout.renderCheckout();
964            }
965        }
966
967        //paytpv - v7.7.1 - Paycomet
968        if (typeof paytpv_initialize !== typeof undefined) {
969            paytpv_initialize();
970        }
971
972        /* Fmmgiftwrapping - v1.1.1 - FMM Modules */
973        if (typeof initColorBox !== typeof undefined) {
974            initColorBox();
975        }
976
977        //pts_stripe - v4.0.6 - PresTeamShop
978        if (typeof AppPTSS !== typeof undefined) {
979            AppPTSS.setup();
980        }
981
982        //klarnapaymentsofficial - v2.2.3 - Prestaworks AB
983        if (typeof paymentList['klarnapaymentsofficial'] !== typeof undefined /*typeof kp_client_token !== typeof undefined*/) {
984            const klarna_options = $('.klarna-container');
985
986            for (const element of klarna_options) {
987                const payment_category = element.id.substring(26, element.id.length);
988                $('input[data-module-name=klarnapayments_' + payment_category + '_module]')
989                    .click(function () {
990                        initiateKlarnaWidget(kp_client_token, '#' + element.id, payment_category);
991                    })
992                    .parents('.module_payment_container').click(function () {
993                        initiateKlarnaWidget(kp_client_token, '#' + element.i
993d, payment_category);
994                    });
995            }
996
997            $('#payment-confirmation > .ps-shown-by-js button').click(function(event) {
998                for (const element of klarna_options) {
999                    const payment_category = element.id.substring(26, element.id.length);
1000                    if ($('input[data-module-name=klarnapayments_' + payment_category + '_module]').is(':checked')) {
1001                        event.preventDefault();
1002                        setupKlarnaAuthCall(payment_category);
1003                        return;
1004                    }
1005                }
1006            });
1007        }
1008
1009        //redsysinsite - v2.2.2 - idnovate
1010        if (typeof paymentList['redsysinsite'] !== typeof undefined) {
1011            let _script = document.createElement('script');
1012            _script.type = 'text/javascript';
1013            _script.src = prestashop.urls.base_url + 'modules/redsysinsite/views/js/front.js';
1014            JqueryElement.append(_script);
1015        }
1016
1017        //klarnapayment - v1.0.4 - Invertus
1018        if (typeof paymentList['klarnapayment'] !== typeof undefined) {
1019            let _script = document.createElement('script');
1020            _script.type = 'text/javascript';
1021            _script.src = prestashop.urls.base_url + 'modules/klarnapayment/views/js/front/payment/payment.js';
1022            JqueryElement.append(_script);
1023        }
1024
1025        if (typeof prestashop !== typeof undefined && typeof prestashop.emit !== typeof undefined) {
1026            prestashop.emit('steco_event_updated');
1027        }
1028
1029        //stripejs - v4.3.9 - NTS
1030        if (typeof paymentList['stripejs'] !== typeof undefined && typeof stripe_allow_cards !== typeof undefined && typeof StripePubKey !== typeof undefined) {
1031            let _script = document.createElement('script');
1032            _script.type = 'text/javascript';
1033            _script.src = prestashop.urls.base_url + 'modules/stripejs/views/js/stripe-prestashop.js';
1034            JqueryElement.append(_script);
1035        }
1036
1037        //kf_paypal - v2.1.7 - KForge
1038        if (typeof paymentList['kfpaypal'] !== typeof undefined) {
1039            let _script = document.createElement('script');
1040            _script.type = 'text/javascript';
1041            _script.src = prestashop.urls.base_url + 'modules/kf_paypal/views/js/front.js';
1042            JqueryElement.append(_script);
1043        }
1044
1045        //mollie - v5.0.1 - Mollie B.V.
1046        if (typeof paymentList['mollie'] !== typeof undefined) {
1047            let $mollieContainers = JqueryElement.find('.mollie-iframe-container');
1048            if ($mollieContainers.length) {
1049                let _script = document.createElement('script');
1050                _script.type = 'text/javascript';
1051                _script.src = prestashop.urls.base_url + 'modules/mollie/views/js/front/mollie_iframe.js';
1052                JqueryElement.append(_script);
1053            }
1054        }
1055
1056        //a4pauthorizenet - v2.0.1 - Addons4Presta
1057        if (typeof paymentList['a4pauthorizenet'] !== typeof undefined) {
1058            let _script = document.createElement('script');
1059            _script.type = 'text/javascript';
1060            _script.src = prestashop.urls.base_url + '/modules/a4pauthorizenet/views/js/a4pauthorizenet.js';
1061            JqueryElement.append(_script);
1062        }
1063
1064        //braintreejs - v3.1.2 - Bellini Services
1065        if (typeof paymentList['braintreejs'] !== typeof undefined) {
1066            if (typeof braintreeHostedSetup !== typeof undefined) {
1067                braintreeHostedSetup();
1068            } else if (typeof braintreePaypalSetup !== typeof undefined) {
1069                braintreePaypalSetup();
1070            } else if (typeof braintreeSetup !== typeof undefined) {
1071                braintreeSetup();
1072            }
1073        }
1074
1075        //support braintreeofficial: v1.2.5 - 202 ecommerce
1076        if (typeof BraintreeSubmitPayment !== typeof undefined) {
1077            var _script = document.createElement('script');
1078            _script.type = 'text/javascript';
1079            _script.src = prestashop.urls.base_url + 'modules/braintreeofficial/views/js/payment_bt.js';
1080            JqueryElement.append(_script);
1081        } else if (typeof BraintreePaypalSubmitPayment !== typeof undefined) {
1082            var _script = document.createElement('script');
1083            _script.type = 'text/javascript';
1084            _script.src = prestashop.urls.base_url + 'modules/braintreeofficial/views/js/payment_pbt.js';
1085            JqueryElement.append(_script);
1086        }
1087
1088        //support paypalplus: v1.2.3 - PixelWeb
1089        if (typeof paymentList['paypalplus'] !== typeof undefined && typeof paypalplus !== typeof undefined) {
1090            $(document).ready(_paypalplus.ready);
1091        }
1092
1093        //revolutpayment - v2.2.4 - Revolut
1094        if (typeof paymentList['revolutpayment'] !== typeof undefined) {
1095            let _script = document.createElement('script');
1096            _script.type = 'text/javascript';
1097            _script.src = prestashop.urls.base_url + '/modules/revolutpayment/views/js/version17/revolut.payment.js';
1098            JqueryElement.append(_script);
1099
1100            let _script2 = document.createElement('script');
1101            _script2.type = 'text/javascript';
1102            _script2.src = prestashop.urls.base_url + '/modules/revolutpayment/views/js/upsell.banner.js';
1103            JqueryElement.append(_script2);
1104        }
1105
1106        //smallpay - v4.0.0 - SmallPay Srl
1107        if (typeof paymentList['smallpay'] !== typeof undefined) {
1108            let _script = document.createElement('script');
1109            _script.type = 'text/javascript';
1110            _script.src = prestashop.urls.base_url + '/modules/smallpay/views/js/front.js';
1111            JqueryElement.append(_script);
1112        }
1113
1114        //payoutloud - v1.2.2 - AdsOutLoud LLC
1115        if (typeof paymentList['Google Pay'] !== typeof undefined && typeof paymentList['Apple Pay'] !== typeof undefined) {
1116            let _script = document.createElement('script');
1117            _script.type = 'text/javascript';
1118            _script.src = prestashop.urls.base_url + '/modules/payoutloud/views/js/payment_request.js';
1119            JqueryElement.append(_script);
1120        }
1121
1122        // verifone - v3.2.1 - Connectors Team - Verifone
1123        if (typeof paymentList['verifone'] !== typeof undefined) {
1124            let _script = document.createElement('script');
1125            _script.type = 'text/javascript';
1126            _script.src = prestashop.urls.base_url + '/modules/verifone/views/assets/js/iframe.js';
1127            JqueryElement.append(_script);
1128        }
1129
1130        // windcavepayments - v2.0.2 - Windcave
1131        if (typeof paymentList['windcavepayments'] !== typeof undefined) {
1132            let _script = document.createElement('script');
1133            _script.type = 'text/javascript';
1134            _script.src = prestashop.urls.base_url + '/modules/windcavepayments/views/js/iframe_payment.js';
1135            JqueryElement.append(_script);
1136        }
1137
1138        // stripe_official - v3.7.2 (compatible v3.0.5+) - Stripe
1139        // Recargar checkout.js sin precaución duplica listeners nativos sobre los
1140        // radios → 2-3 mounts del paymentElement por click. Solución:
1141        //   - Clonar payment-option con $.clone(true,true) (preserva jQuery, descarta
1142        //     addEventListener nativo) y reemplazar.
1143        //   - Dejarlas unchecked: el IIFE recargado evita su self-trigger de 5s y
1144        //     dispara init exactamente 1 vez vía paymentFormInput.click() interno.
1145        //   - Inyectar <label for="<radio-id>"> oculto: la tpl PS envuelve el radio
1146        //     en <label> sin for=, lo que rompía setPaymentOptionsLoading() y dejaba
1147        //     los radios disabled.
1148        //   - Spinner sibling de #js-stripe-payment-element (un MutationObserver lo
1149        //     retira al aparecer el iframe) para feedback de carga.
1150        if (typeof paymentList['stripe_official'] !== typeof undefined && compareVersion(paymentList["stripe_official"], '3.0.4') === 1) {
1151            window.stripe_official_loaded = false;
1152
1153            $(document).off('change.stripe_opc').on('change.stripe_opc', '[data-module-name="stripe_official"]', function() {
1154                if (window.stripe_official_loaded) {
1155                    return;
1156                }
1157                window.stripe_official_loaded = true;
1158
1159                $('input[name="payment-option"]').each(function () {
1160                    const $original = $(this);
1161                    const $clone = $original.clone(true, true);
1162                    $original.replaceWith($clone);
1163
1164                    const radioId = $clone.attr('id');
1165                    if (radioId && !document.querySelector('label[for="' + radioId + '"]')) {
1166                        const $hiddenLabel = $('<label aria-hidden="true" style="display:none"></label>')
1167                            .attr('for', radioId);
1168                        const $wrappingLabel = $clone.closest('label');
1169                        const $insertionPoint = $wrappingLabel.length ? $wrappingLabel : $clone;
1170                        $insertionPoint.after($hiddenLabel);
1171                    }
1172                });
1173
1174                // Stripe cross-compat: the clone+replaceWith above replaces every
1175                // payment radio with a fresh node, orphaning the direct .on('change')
1176                // listeners that eventClickPaymentContainer() bound to the originals.
1177                // Those listeners manage the .selected CSS class on .payment-option
1178                // and .module_payment_container. Without rebinding, switching away from
1179                // Stripe leaves the Stripe container permanently marked as selected.
1180                // Fix: rebind a change handler to the new clone nodes and immediately
1181                // sync the .selected state to whichever radio is currently checked.
1182                $('input[name="payment-option"]').on('change.stripe_opc_selected', function () {
1183                    $('.payment-option, .module_payment_container').removeClass('selected');
1184                    $(this).parents('.payment-option, .module_payment_container').addClass('selected');
1185                });
1186                // Sync current state now so the Stripe container shows as selected
1187                // (it was just selected to trigger this block) and everything else is clear.
1188                (function () {
1189                    const $checked = $('input[name="payment-option"]:checked');
1190                    $('.payment-option, .module_payment_container').removeClass('selected');
1191                    $checked.parents('.payment-option, .module_payment_container').addClass('selected');
1192                }());
1193
1194                // paypal - cross-compat: el clone(true,true)+replaceWith de arriba
1195                // reemplaza TODOS los radios de pago, incluido el de PayPal. PaypalTools
1196                // (shortcut.js) guarda referencias directas a esos nodos <input> en
1197                // window.paypalTools{Show,Hidden}ElemenList y, en su listener 'input',
1198                // hace display = trigger.checked ? 'block' : 'none' sobre
1199                // [paypal-button-container]. Tras el reemplazo esas referencias apuntan a
1200                // nodos desconectados del DOM (checked siempre false) -> al re-seleccionar
1201                // PayPal el boton queda en display:none. A diferencia de la rama de reload
1202                // AJAX (que re-inyecta shortcut.js y re-registra los nodos), aqui no se
1203                // recarga el listado, asi que remapeamos cada referencia muerta a su
1204                // reemplazo vivo (mismo id / data-module-name) y re-sincronizamos.
1205                if (typeof paymentList['paypal'] !== typeof undefined) {
1206                    const remapPaypalRefs = function (listName) {
1207                        if (typeof window[listName] === typeof undefined || !window[listName]) {
1208                            return;
1209                        }
1210                        Object.keys(window[listName]).forEach(function (key) {
1211                            window[listName][key] = window[listName][key]
1212                                .map(function (el) {
1213                                    if (!el || document.body.contains(el)) {
1214                                        return el;
1215                                    }
1216                                    let replacement = el.id ? document.getElementById(el.id) : null;
1217                                    if (!replacement && typeof el.getAttribute === 'function') {
1218                                        const moduleName = el.getAttribute('data-module-name');
1219                                        if (moduleName) {
1220                                            replacement = document.querySelector(
1221                                                'input[name="payment-option"][data-module-name="' + moduleName + '"]'
1222                                            );
1223                                        }
1224                                    }
1225                                    return replacement || el;
1226                                })
1227                                .filter(function (el) {
1228                                    return el && document.body.contains(el);
1229                                });
1230                        });
1231                    };
1232
1233                    remapPaypalRefs('paypalToolsShowElemenList');
1234                    remapPaypalRefs('paypalToolsHiddenElemenList');
1235
1236                    if (typeof window.PaypalTools !== typeof undefined && window.PaypalTools) {
1237                        if (typeof window.PaypalTools.showElementsIfChecked === 'function') {
1238                            window.PaypalTools.showElementsIfChecked();
1239                        }
1240                        if (typeof window.PaypalTools.hideElementTillChecked === 'function') {
1241                            window.PaypalTools.hideElementTillChecked();
1242                        }
1243                    }
1244                }
1245
1246                let _script = document.createElement('script');
1247                _script.type = 'text/javascript';
1248                _script.src = prestashop.urls.base_url + '/modules/stripe_official/views/js/checkout.js';
1249                JqueryElement.append(_script);
1250
1251                const stripePaymentEl = document.getElementById('js-stripe-payment-element');
1252                if (stripePaymentEl && !document.getElementById('opc-stripe-loading')) {
1253                    if (!document.getElementById('opc-stripe-loading-style')) {
1254                        const styleEl = document.createElement('style');
1255                        styleEl.id = 'opc-stripe-loading-style';
1256                        styleEl.textContent = '@keyframes opcStripeSpin{to{transform:rotate(360deg)}}'
1257                            + '#opc-stripe-loading{display:flex;align-items:center;justify-content:center;'
1258                            + 'gap:0.6rem;padding:1.5rem;color:#6c757d;font-size:0.9em;}'
1259                            + '#opc-stripe-loading .opc-stripe-spinner{display:inline-block;width:1.1em;'
1260                            + 'height:1.1em;border:2px solid #d1d5db;border-top-color:#4f46e5;'
1261                            + 'border-radius:50%;animation:opcStripeSpin 0.8s linear infinite;}';
1262                        document.head.appendChild(styleEl);
1263                    }
1264
1265                    const loadingText = (typeof OPC !== 'undefined' && OPC.Message && OPC.Message.loadingPaymentForm)
1266                        || 'Loading payment form...';
1267                    const loader = document.createElement('div');
1268                    loader.id = 'opc-stripe-loading';
1269                    loader.innerHTML = '<span class="opc-stripe-spinner"></span><span></span>';
1270                    loader.lastElementChild.textContent = loadingText;
1271                    stripePaymentEl.parentNode.insertBefore(loader, stripePaymentEl);
1272
1273                    const removeLoader = () => {
1274                        if (loader.parentNode) {
1275                            loader.parentNode.removeChild(loader);
1276                        }
1277                    };
1278
1279                    const stripeLoadingObserver = new MutationObserver(() => {
1280                        if (stripePaymentEl.querySelector('iframe')) {
1281                            removeLoader();
1282                            stripeLoadingObserver.disconnect();
1283                        }
1284                    });
1285                    stripeLoadingObserver.observe(stripePaymentEl, { childList: true, subtree: true });
1286
1287                    setTimeout(() => {
1288                        removeLoader();
1289                        stripeLoadingObserver.disconnect();
1290                    }, 15000);
1291                }
1292            });
1293        }
1294
1295        if (typeof window.StripeOfficialInitExpressCheckout === 'function') {
1296            window.StripeOfficialInitExpressCheckout(JqueryElement);
1297        }
1298
1299        //nexixpay - v7.0.5 - Nexi Payments SpA
1300        if (typeof paymentList['nexixpay'] !== typeof undefined) {
1301            let _script = document.createElement('script');
1302            _script.type = 'text/javascript';
1303            _script.src = prestashop.urls.base_url + '/modules/nexixpay/views/js/front_xpay.js';
1304            JqueryElement.append(_script);
1305        }
1306
1307        // paypal - v6.6.0 - 202 ecommerce
1308        // Duplicación de [paypal-mark-container] (logos) y .paypal-buttons (smart button
1309        // iframes) en AJAX reload del listado de pagos. `Shortcut.addMarkTo` y
1310        // `Shortcut.initButton` añaden sin comprobar instancias previas; el HTML del
1311        // additional-information re-inyecta `<script src="shortcut.js" data-key="shortcut">`
1312        // en cada reload, lo que provoca que el navegador vuelva a cargar shortcut.js y
1313        // re-asigne `window.Shortcut` (perdiendo cualquier patch previo
1313). Mismo patrón
1314        // con `bnpl.js`/`window.BNPL`.
1315        //
1316        // "Detected container element removed from DOM": the PayPal SDK v5 fires this
1317        // entirely inside its own render() call - the full error stack is PayPal-internal
1318        // with no OPC frames. It cannot be caught by window.onerror, unhandledrejection,
1319        // or initButton() guards. The only interception point is window.paypal.Buttons
1320        // itself: wrap its render() to bail silently when the container is already detached.
1321        if (!window._opcPaypalButtonsPatch) {
1322            window._opcPaypalButtonsPatch = true;
1323            var _opcPatchPaypalButtons = function () {
1324                if (!window.paypal || !window.paypal.Buttons) return false;
1325                if (window.paypal.Buttons._opcPatched) return true;
1326                var origButtons = window.paypal.Buttons.bind(window.paypal);
1327                window.paypal.Buttons = function () {
1328                    var component = origButtons.apply(this, arguments);
1329                    if (component && typeof component.render === 'function' && !component.render._opcPatched) {
1330                        var origRender = component.render.bind(component);
1331                        component.render = function (container) {
1332                            var el = typeof container === 'string' ? document.querySelector(container) : container;
1333                            if (el instanceof Element && !document.body.contains(el)) {
1334                                return Promise.resolve();
1335                            }
1336                            return origRender(container);
1337                        };
1338                        component.render._opcPatched = true;
1339                    }
1340                    return component;
1341                };
1342                // Copy over SDK properties (Marks, Messages, eligibility, etc.)
1343                Object.keys(origButtons).forEach(function (key) {
1344                    try { window.paypal.Buttons[key] = origButtons[key]; } catch (e) {}
1345                });
1346                window.paypal.Buttons._opcPatched = true;
1347                return true;
1348            };
1349            if (!_opcPatchPaypalButtons()) {
1350                // window.paypal loads async - poll until SDK is ready
1351                var _opcPaypalPollAttempts = 40;
1352                var _opcPaypalPollInterval = setInterval(function () {
1353                    _opcPaypalPollAttempts -= 1;
1354                    if (_opcPatchPaypalButtons() || _opcPaypalPollAttempts <= 0) {
1355                        clearInterval(_opcPaypalPollInterval);
1356                    }
1357                }, 250);
1358            }
1359        }
1360
1361        if (typeof paymentList['paypal'] !== typeof undefined) {
1362            if (JqueryElement.find('#ppplus-mb').length > 0) {
1363                let _script = document.createElement('script');
1364                _script.type = 'text/javascript';
1365                _script.src = prestashop.urls.base_url + '/modules/paypal/views/js/payment_mb.js?v=6.5.1';
1366                JqueryElement.append(_script);
1367            }
1368            // 1. Limpieza reactiva: eliminar [paypal-mark-container] duplicados ya en DOM
1369            JqueryElement.find('[paypal-mark-container]').parent().each(function () {
1370                const $marks = $(this).children('[paypal-mark-container]');
1371                if ($marks.length > 1) {
1372                    $marks.not(':first').remove();
1373                }
1374            });
1375
1376            // 2. Misma limpieza para los smart-button iframes (zoid-paypal-buttons-*).
1377            // v6.6.0: cada llamada a Shortcut.initButton() / BNPL.initButton() llama a
1378            // SDK.Buttons({...}).render(this.button) que apendea otro .paypal-buttons.
1379            JqueryElement.find('[paypal-button-container], [paypal-bnpl-button-container]').each(function () {
1380                const $buttons = $(this).children('.paypal-buttons');
1381                if ($buttons.length > 1) {
1382                    $buttons.not(':first').remove();
1383                }
1384            });
1385
1386            // 3. Patch proactivo: wrap addMarkTo/initButton sobre Shortcut y BNPL para
1387            // limpiar antes de renderizar. La guard `_opcPatched` la ponemos en la
1388            // función wrapper (no en el objeto Shortcut/BNPL): cuando shortcut.js se
1389            // re-carga y reasigna obj.addMarkTo al original, nuestra guard ya no aparece
1390            // y volvemos a parchear. Si el objeto aún no existe (script async), reintento
1391            // con polling hasta 5s.
1392            const patchPaypalHelper = function (objName) {
1393                const obj = window[objName];
1394                if (!obj) {
1395                    return false;
1396                }
1397
1398                if (typeof obj.addMarkTo === 'function' && !obj.addMarkTo._opcPatched) {
1399                    const origAddMarkTo = obj.addMarkTo.bind(obj);
1400                    const wrapped = function (container, styles) {
1401                        if (container instanceof Element) {
1402                            container.querySelectorAll('[paypal-mark-container]').forEach(function (el) {
1403                                el.remove();
1404                            });
1405                        }
1406                        return origAddMarkTo(container, styles);
1407                    };
1408                    wrapped._opcPatched = true;
1409                    obj.addMarkTo = wrapped;
1410                }
1411
1412                if (typeof obj.initButton === 'function' && !obj.initButton._opcPatched) {
1413                    const origInitButton = obj.initButton.bind(obj);
1414                    const wrapped = function () {
1415                        // Solo bloqueamos si el contenedor ya no esta en el DOM.
1416                        // NO eliminamos hijos .paypal-buttons aqui: el SDK observa sus
1417                        // propios iframes via MutationObserver y lanza internamente
1418                        // "Detected container element removed from DOM" cuando detecta
1419                        // que un hijo .paypal-buttons es removido - ese el error que
1420                        // queremos suprimir. La limpieza de duplicados se hace en el
1421                        // paso reactivo (seccion 2) antes de que el SDK este montado.
1422                        if (obj.button instanceof Element && !document.body.contains(obj.button)) {
1423                            return undefined;
1424                        }
1425                        try {
1426                            return origInitButton.apply(this, arguments);
1427                        } catch (e) {
1428                            return undefined;
1429                        }
1430                    };
1431                    wrapped._opcPatched = true;
1432                    obj.initButton = wrapped;
1433                }
1434
1435                return true;
1436            };
1437
1438            ['Shortcut', 'BNPL'].forEach(function (objName) {
1439                if (patchPaypalHelper(objName)) {
1440                    return;
1441                }
1442                let attempts = 25;
1443                const interval = setInterval(function () {
1444                    attempts -= 1;
1445                    if (patchPaypalHelper(objName) || attempts <= 0) {
1446                        clearInterval(interval);
1447                    }
1448                }, 200);
1449            });
1450
1451            // 3. Restaurar listeners de visibilidad de PayPal en el nuevo contenedor de pagos
1452            // Problema: PaypalTools registra el listener 'input' en el contenedor de pagos original.
1453            // Tras el reload AJAX ese contenedor es reemplazado, el listener queda en el nodo
1454            // antiguo (desconectado del DOM), y la visibilidad de #payment-confirmation deja de
1455            // actualizarse al seleccionar/deseleccionar PayPal.
1456            if (typeof window.PaypalTools !== typeof undefined) {
1457                const $newPaymentContainer = JqueryElement.find('.payment-options, .payment__list');
1458                const newContainer = $newPaymentContainer[0];
1459
1460                if (typeof window.paypalToolsHiddenElemenList !== typeof undefined) {
1461                    // Eliminar referencias a nodos ya removidos del DOM
1462                    Object.keys(window.paypalToolsHiddenElemenList).forEach(function (key) {
1463                        window.paypalToolsHiddenElemenList[key] = window.paypalToolsHiddenElemenList[key].filter(
1464                            function (el) { return document.body.contains(el); }
1465                        );
1466                    });
1467                    // Sincronizar estado inicial de visibilidad
1468                    window.PaypalTools.hideElementTillChecked();
1469                    // Re-vincular listener al nuevo contenedor de pagos
1470                    if (newContainer instanceof Element) {
1471                        newContainer.addEventListener('input', window.PaypalTools.hideElementTillChecked);
1472                    }
1473                }
1474
1475                if (typeof window.paypalToolsShowElemenList !== typeof undefined) {
1476                    // Eliminar referencias a nodos ya removidos del DOM
1477                    Object.keys(window.paypalToolsShowElemenList).forEach(function (key) {
1478                        window.paypalToolsShowElemenList[key] = window.paypalToolsShowElemenList[key].filter(
1479                            function (el) { return document.body.contains(el); }
1480                        );
1481                    });
1482                    // Sincronizar estado inicial de visibilidad
1483                    window.PaypalTools.showElementsIfChecked();
1484                    // Re-vincular listener al nuevo contenedor de pagos
1485                    if (newContainer instanceof Element) {
1486                        newContainer.addEventListener('input', window.PaypalTools.showElementsIfChecked);
1487                    }
1488                }
1489            }
1490        }
1491
1492        // tfauthorizedotnet - v7.2.0 - presta_world
1493        if (typeof paymentList['tfauthorizedotnet'] !== typeof undefined) {
1494            let _script = document.createElement('script');
1495            _script.type = 'text/javascript';
1496            _script.src = prestashop.urls.base_url + 'modules/tfauthorizedotnet/views/js/front/DatPayment.js';
1497            JqueryElement.append(_script);
1498        }
1499
1500        // mercadopago - v4.18.5 - mercadopago
1501        // Cada sub-checkout (custom/ticket/pse/standard-modal) inyecta vía AJAX un
1502        // <script src="https://sdk.mercadopago.com/js/v2"> + scripts internos +
1503        // un inline `window.onload = loadXxx();` que asume las dependencias YA
1504        // cargadas. En contexto AJAX los <script src> son asíncronos: el inline
1505        // corre antes que el SDK v2/initializeXxx → "ReferenceError: MercadoPago
1506        // is not defined". Reintentamos cada loadXxx con polling hasta que las
1507        // dependencias estén disponibles (máx 5s).
1508        if (typeof paymentList['mercadopago'] !== typeof undefined) {
1509            const mpInits = [
1510                { fn: 'loadCustom', needs: ['MercadoPago', 'initializeCustom'] },
1511                { fn: 'loadTicket', needs: ['mpValidateSellerInfo', 'validateDocumentInputs', 'mercadoPagoFormHandlerTicket'] }
1512            ];
1513
1514            mpInits.forEach(({ fn, needs }) => {
1515                if (typeof window[fn] !== 'function') {
1516                    return;
1517                }
1518
1519                const tryRun = (attemptsLeft) => {
1520                    const ready = needs.every((dep) => typeof window[dep] !== 'undefined');
1521                    if (ready) {
1522                        try {
1523                            window[fn]();
1524                        } catch (e) {
1525                            // No bloqueamos otros patches si una pasarela rompe.
1526                            // eslint-disable-next-line no-console
1527                            console.warn('[OPC][mercadopago] ' + fn + ' falló', e);
1528                        }
1529                        return;
1530                    }
1531                    if (attemptsLeft > 0) {
1532                        setTimeout(() => tryRun(attemptsLeft - 1), 100);
1533                    }
1534                };
1535
1536                tryRun(50);
1537            });
1538        }
1539
1540        // bridge - v1.0.13 - 202 ecommerce
1541        if (typeof paymentList['bridge'] !== typeof undefined) {
1542            let _script = document.createElement('script');
1543            _script.type = 'text/javascript';
1544            _script.src = prestashop.urls.base_url + '/modules/bridge/views/js/front/front.js';
1545            JqueryElement.append(_script);
1546        }
1547
1548        // wkcodextrafee - v7.0.1 - wkcodextrafee
1549        if (typeof paymentList['wkcodextrafee'] !== typeof undefined) {
1550            let _script = document.createElement('script');
1551            _script.type = 'text/javascript';
1552            _script.src = prestashop.urls.base_url + '/modules/wkcodextrafee/views/js/wkcodpayment.js';
1553            JqueryElement.append(_script);
1554        }
1555
1556        // everypay - v8.0.1 - everypay
1557        if (typeof paymentList['everypay'] !== typeof undefined && typeof EVERYPAY !== typeof undefined && typeof EVERYPAY.init !== typeof undefined) {
1558            EVERYPAY.init();
1559        }
1560
1561        //btgooglepay - v1.1.2 - Business Tech
1562        //El módulo intenta renderizar el botón en DOMContentLoaded, antes de que OPC
1563        //termine de cargar las opciones de pago vía AJAX. El container #google-pay-button
1564        //no existe todavía y btgooglepay aborta con "Container #google-pay-button 
1564not found".
1565        //Re-inicializamos cuando OPC completa el render y el container ya está en el DOM.
1566        if (typeof paymentList['btgooglepay'] !== typeof undefined
1567            && typeof window.initGooglePay === 'function'
1568            && typeof window.waitForGooglePay === 'function'
1569            && JqueryElement.find('#google-pay-button').length > 0
1570        ) {
1571            window.waitForGooglePay(window.initGooglePay);
1572        }
1573
1574        if (Object.keys(OPC.Compatibilities).length > 0) {
1575            if (typeof OPC.Compatibilities.orderfees_payment !== typeof undefined) {
1576                JqueryElement.find('input[name="payment-option"]').on('change', () => {
1577                    prestashop.emit('opc-cart-getCartSummary', {loadPayment: false, loadShipping: false})
1578                });
1579            }
1580        }
1581    });
1582
1583    prestashop.on('opc-payment-toggleOrderButton-after', (params) => {
1584        const { JqueryElement, selectedOption, confirmationSelector, showConfirmationSelector } = params;
1585
1586        //wkpaymentfee - v6.1.1 - Webkul
1587        if (typeof managePaymentFeeForMethod !== typeof undefined) {
1588            managePaymentFeeForMethod(JqueryElement.find('input[name="payment-option"]:checked'));
1589        }
1590
1591        // systempay - v1.23.1 - Lyra Network
1592        if (typeof window.KR !== typeof undefined && typeof selectedOption !== typeof undefined) {
1593            const $additionalInfo = JqueryElement.find('#' + selectedOption + '-additional-information');
1594            if ($additionalInfo.find('[kr-form-token]').length > 0) {
1595                window.KR.renderElements('[kr-form-token]').catch(() => {});
1596            }
1597        }
1598
1599        //ps_checkout - v8.5.2.0 - PrestaShop
1600        //Mostrar el botón de submit del SDK (#opc-checkout-finalize) dentro de la sección binary.
1601        //Para el funding source 'card' con Hosted Fields habilitados (cardFields.tpl), el SDK
1602        //renderiza los iframes de tarjeta dentro de #pay-with-{option.id}-form. OPC oculta ese
1603        //form (clase ps-hidden) cuando el radio es binary; lo forzamos visible sólo para card.
1604        //Nota: option.id lo autogenera PrestaShop ('payment-option-N'), no coincide con
1605        //module_name, por eso identificamos la selección vía data-module-name.
1606        if (typeof window.ps_checkout !== typeof undefined && typeof JqueryElement !== typeof undefined) {
1607            JqueryElement.find('.js-payment-binary #opc-checkout-finalize').show();
1608
1609            const $selectedRadio = JqueryElement.find('input[name="payment-option"]:checked');
1610            if ($selectedRadio.data('module-name') === 'ps_checkout-card') {
1611                JqueryElement.find('#pay-with-' + selectedOption + '-form')
1612                    .removeClass('ps-hidden')
1613                    .css('display', 'block');
1614            }
1615        }
1616
1617        // paypal - v6.2.1 - 202 ecommerce
1618        const paypalEcWrongButtonMessage = JqueryElement.find('[paypal-ec-wrong-button-message]');
1619        if (paypalEcWrongButtonMessage.length > 0) {
1620            if (JqueryElement.find('input[name="payment-option"]:checked').attr("data-module-name") === 'paypal') {
1621                JqueryElement.find(confirmationSelector + ' button').on('click', function(){
1622                    event.preventDefault();
1623                    event.stopPropagation();
1624                    paypalEcWrongButtonMessage.show();
1625                });
1626            } else {
1627                JqueryElement.find(confirmationSelector + ' button').off('click');
1628            }
1629        }
1630
1631        //ets_payment_with_fee - v2.2.9 - ETS-Soft.
1632        if (typeof ets_cookie_module_name !== typeof undefined) {
1633            ets_cookie_payment_option = false;
1634            ajaxChangePaymentMethod = function (){};
1635
1636            if (typeof ajaxChangePaymentMethodOPC !== typeof undefined) {
1637                ajaxChangePaymentMethodOPC(params);
1638            } else {
1639                ajaxChangePaymentMethodOPC = function(params) {
1640                    const { JqueryElement, selectedOption } = params;
1641
1642                    if (typeof selectedOption !== typeof undefined) {
1643                        const $paymentSelected = JqueryElement.find('input[name="payment-option"]:checked');
1644                        const moduleName = $paymentSelected.data('module-name');
1645                        let paymentMethodId = 0;
1646
1647                        if (JqueryElement.find('#pay-with-' + selectedOption + '-form input[name="id_payment_method"]').length > 0) {
1648                            paymentMethodId = JqueryElement.find('#pay-with-' + selectedOption + '-form input[name="id_payment_method"]').val();
1649                        }
1650
1651                        $.ajax({
1652                            url: '',
1653                            data: 'ets_set_payment_option=1&module_name=' + moduleName + '&id_payment_method=' + paymentMethodId + '&payment_option=' + selectedOption,
1654                            type: 'post',
1655                            dataType: 'json',
1656                            beforeSend: function () {
1657                                JqueryElement.append($('<div class="loading-opc"/>'));
1658                            },
1659                            complete: function () {
1660                                JqueryElement.find('.loading-opc').remove();
1661                            }
1662                        });
1663                    }
1664                };
1665
1666                ajaxChangePaymentMethodOPC(params);
1667            }
1668        }
1669
1670        //hipay_enterprise - v2.17.0 - HiPay
1671        if (typeof setSelectedPaymentMethod !== typeof undefined) {
1672            setSelectedPaymentMethod();
1673        }
1674
1675        //revolutpayment - v2.2.4 - Revolut
1676        if (typeof JqueryElement.find('input#' + selectedOption) !== typeof undefined) {
1677            if (JqueryElement.find('input#' + selectedOption).data('module-name') === 'Revolut Pay - Get Cashback'
1678                || (JqueryElement.find('input#' + selectedOption).val() === 'revolutpayment'
1679                    && JqueryElement.find('input#' + selectedOption).closest('.module_payment_container').find('#revolutPayForm button').length > 0
1680                )
1681            ) {
1682                JqueryElement.find(confirmationSelector + ' button').addClass('revolut_hide');
1683            } else if (showConfirmationSelector) {
1684                JqueryElement.find(confirmationSelector + ' button').removeClass('revolut_hide');
1685            }
1686        }
1687
1688        // paypalfeeplus - v1.3.2 - idnovate
1689        if (typeof movePPFPElements !== typeof undefined) {
1690            setInterval(() => {
1691                movePPFPElements();
1692            }, 1000);
1693        }
1694    });
1695
1696    prestashop.on('opc-payment-toggleOrderButton-validation', (params) => {
1697        const { JqueryElementMain, JqueryElement, JqueryElementShipping, confirmationAlertSelector } = params;
1698
1699        const extraContentCarrierSelected = JqueryElementShipping.find('.delivery_option_radio:checked').parents('.delivery-option').next();
1700
1701        //Reset
1702        if (intervalPaymentToggleOrderButtonValidation !== null) {
1703            clearInterval(intervalPaymentToggleOrderButtonValidation);
1704        }
1705        JqueryElement.data('opc-payment-toggleOrderButton-validation', true);
1706
1707        if (JqueryElementMain.find('#notifications > .alert').length > 0) {
1708            JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1709            $(confirmationAlertSelector).text(JqueryElementMain.find('#notifications > .alert').text());
1710        }
1711
1712        // correosexpress - v5.5.2 - IT Department
1713        const $elementCEX = $('#CEX');
1714        if ($elementCEX.length > 0) {
1715            const hasOfficeProduct = $('input[name*=delivery_option]:checked').attr('producto');
1716            if (hasOfficeProduct !== undefined) {
1717                if ($elementCEX.find('#texto_producto').length > 0) {
1718                    JqueryElement.data('opc-payment-toggleOrderButton-validation', true);
1719                } else {
1720                    JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1721                    $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1722                }
1723            }
1724        }
1725
1726        // gelproximity - v1.2.9 - iModuli.it
1727        if (typeof gelprx_conf !== typeof undefined && $('#gelproximity-app-no-pick-point-selected').length > 0) {
1728            $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1729            JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1730        }
1731
1732        //slmshipping - v1.0.3 - de Skroutz
1733        if (typeof _skroutzPointsMapWidgetOptions !== typeof undefined) {
1734            if ($('[data-target="#SkroutzMapModal"]').is(':visible')) {
1735                const skroutzPointId = jQuery('[name="skroutz_point_id"]').val();
1736
1737                if ($.trim(skroutzPointId) == '' || $.trim(skroutzPointId) == null) {
1738                    $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1739
1740                    JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1741                }
1742            }
1743        }
1744
1745        /* Smartpost Shipping - v1.2.13 - Mijora.lt */
1746        if (typeof ItellaModule !== typeof undefined) {
1747            const deliveryIdSelected = JqueryElementShipping.find('.delivery_option_radio:checked').val();
1748            if (deliveryIdSelected == itella_carrier_pickup_id + ',' && $('#itella_pickup_point_id').val() === '') {
1749                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1750
1751                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1752            }
1753        }
1754
1755        /* Venipak Shipping - v1.1.5 - mijora.lt */
1756        if (document.getElementById("mjvp-selected-terminal") !== null) {
1757            const selected_terminal = document.getElementById("mjvp-selected-terminal").value;
1758
1759            if ($('.mjvp-pp-container').is(':visible') && (selected_terminal == '' || selected_terminal == null || selected_terminal == 0)) {
1760                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1761
1762                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1763            }
1764        }
1765
1766        /* Omniva Shipping - v2.0.20 - Mijora */
1767        if (typeof omnivalt_params !== typeof undefined) {
1768            const deliveryIdSelected = JqueryElementShipping.find('.delivery_option_radio:checked').val();
1769
1770            if (deliveryIdSelected == omnivalt_params.methods.omniva_terminal + ',' && $('select[name="omnivalt_parcel_terminal"]').val() == "") {
1771                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1772
1773                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1774            }
1775        }
1776
1777        //glsshipping - v3.7.0 - GLS
1778        // selectPickup.js rellena #parcel_codigo vía postMessage del iframe y hace
1779        // un AJAX `?method=saveParcel` fire-and-forget sin emitir eventos PS. El
1780        // trigger('click') sobre payment-option no basta: si el cliente eligió el
1781        // carrier GLS Parcel Shop antes de seleccionar método de pago, no hay
1782        // :checked y la re-evaluación nunca corre. Forzamos getPaymentList para
1783        // re-rendear el bloque payment y disparar toggleOrderButton de nuevo.
1784        if (JqueryElementShipping.find('#parcelshopid').length > 0) {
1785            const parcelShopId = parseInt(JqueryElementShipping.find('#parcelshopid').val());
1786            const deliveryIdSelected = parseInt(JqueryElementShipping.find('.delivery_option_radio:checked').val());
1787
1788            if (parcelShopId === deliveryIdSelected && JqueryElementShipping.find('#parcel_codigo').val() === '') {
1789                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1790
1791                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1792
1793                intervalPaymentToggleOrderButtonValidation = setInterval(function () {
1794                    if (JqueryElementShipping.find('#parcel_codigo').val() !== '') {
1795                        clearInterval(intervalPaymentToggleOrderButtonValidation);
1796
1797                        prestashop.emit('opc-payment-getPaymentList');
1798                    }
1799                }, 1000);
1800            } else {
1801                JqueryElement.data('opc-payment-toggleOrderButton-validation', true);
1802            }
1803        }
1804
1805        //gmparcellocker - v1.4.1 - GreenMouseStudio.com
1806        if (typeof window.gmparcellockerStoreSelected !== typeof undefined) {
1807            if (!window.gmparcellockerStoreSelected) {
1808                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1809
1810                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1811
1812                intervalPaymentToggleOrderButtonValidation = setInterval(function () {
1813                    if (window.gmparcellockerStoreSelected) {
1814                        JqueryElement.find('input[name="payment-option"]:checked').trigger('click');
1815
1816                        clearInterval(intervalPaymentToggleOrderButtonValidation);
1817                    }
1818                }, 1000);
1819            } else {
1820                JqueryElement.data('opc-payment-toggleOrderButton-validation', true);
1821            }
1822        }
1823
1824        //samedaycourier - v1.8.5 - Sameday Courier
1825        // Map mode renders #locker_name; selector mode (lockers map off) renders #lockerIdSelector.
1826        var samedayLockerField = extraContentCarrierSelected.find('#locker_name').length
1827            ? extraContentCarrierSelected.find('#locker_name')
1828            : extraContentCarrierSelected.find('#lockerIdSelector');
1829
1830        if (samedayLockerField.length) {
1831            if (samedayLockerField.val() === '' || typeof samedayLockerField.val() === typeof undefined) {
1832                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1833
1834                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1835
1836                intervalPaymentToggleOrderButtonValidation = setInterval(function () {
1837                    if (samedayLockerField.val() !== '' && typeof samedayLockerField.val() !== typeof undefined) {
1838                        JqueryElement.find('input[name="payment-option"]:checked').trigger('click');
1839
1840                        clearInterval(intervalPaymentToggleOrderButtonValidation);
1841                    }
1842                }, 1000);
1843            } else {
1844                JqueryElement.data('opc-payment-toggleOrderButton-validation', true);
1845            }
1846        }
1847
1848        //boxnow - v2.x - v3.1.0 - BoxNow
1849        if (extraContentCarrierSelected.find('#boxnow-map-container').length > 0) {
1850            const boxnowZipValue = extraContentCarrierSelected.find('.boxnow-zip').val();
1851            if (typeof boxnowZipValue !== typeof undefined && boxnowZipValue === '') {
1852                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1853
1854                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1855
1856                intervalPaymentToggleOrderButtonValidation = setInterval(function () {
1857                    const updatedZipValue = extraContentCarrierSelected.find('.boxnow-zip').val();
1858                    if (typeof updatedZipValue !== typeof undefined && updatedZipValue !== '') {
1859                        JqueryElement.find('input[name="payment-option"]:checked').trigger('click');
1860
1861                        clearInterval(intervalPaymentToggleOrderButtonValidation);
1862                    }
1863                }, 1000);
1864            } else {
1865                JqueryElement.data('opc-payment-toggleOrderButton-validation', true);
1866            }
1867        }
1868
1869        /* chronopost - v7.5.6 - Chronopost Official */
1870        if (typeof CHRONORELAIS_ID !== 'undefined') {
1871            const deliveryIdSelected = JqueryElementShipping.find('.delivery_option_radio:checked').val();
1872            const selectedCarrierId = deliveryIdSelected ? deliveryIdSelected.substr(0, deliveryIdSelected.indexOf(',')) : '';
1873            const chronoRelaisIds = [CHRONORELAIS_ID, CHRONORELAIS_AMBIENT_ID, RELAISEUROPE_ID, RELAISDOM_ID, TOSHOPDIRECT_ID, TOSHOPDIRECT_EUROPE_ID].filter(id => id !== '-1');
1874            if (chronoRelaisIds.includes(selectedCarrierId) && !$('input[name="chronorelaisSelect"]:checked').val()) {
1875                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1876                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1877            }
1878        }
1879
1880        // dpdfrance - v6.5.1 - DPD France S.A.S
1881        if (typeof dpdfranceRelaisCarrierId !== 'undefined') {
1882            const deliveryIdSelected = JqueryElementShipping.find('.delivery_option_radio:checked').val();
1883            const selectedCarrierId = deliveryIdSelected ? deliveryIdSelected.substr(0, deliveryIdSelected.indexOf(',')) : '';
1884            // Cubre los tres tipos de relay: global, locker (taquillas) y retail
1885            const dpdRelaisIds = [
1886                dpdfranceRelaisCarrierId,
1887                typeof dpdfranceRelaisLockerCarrierId !== 'undefined' ? dpdfranceRelaisLockerCarrierId : null,
1888                typeof dpdfranceRelaisRetailCarrierId !== 'undefined' ? dpdfranceRelaisRetailCarrierId : null,
1889            ].filter(Boolean);
1890            if (dpdRelaisIds.includes(selectedCarrierId)) {
1891                // Determinar qué tabla relay está visible (retail > locker > relais)
1892                let dpdActiveTable = 'table#dpdfrance_relais_point_table';
1893                if ($('table#dpdfrance_retail_point_table').is(':visible')) {
1894                    dpdActiveTable = 'table#dpdfrance_retail_point_table';
1895                } else if ($('table#dpdfrance_locker_point_table').is(':visible')) {
1896                    dpdActiveTable = 'table#dpdfrance_locker_point_table';
1897                }
1898                // Soporta versión antigua (:checked) y nueva (data-relay-select=true)
1899                const selectedPUDO = $(dpdActiveTable + ' [name=dpdfrance_relay_id]:checked, ' + dpdActiveTable + ' [name=dpdfrance_relay_id][data-relay-select="true"]');
1900                if (!selectedPUDO.length || $(dpdActiveTable + ' .dpdfrance_relais_error').length > 0) {
1901                    JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1902                    $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1903                }
1904            }
1905        }
1906
1907        // econtdelivery - v2.0.3 - ITPartner
1908        // Bloquea el botón de pago si Econt está seleccionado pero no se ha elegido punto de entrega.
1909        if (JqueryElementShipping.find('#econt_delivery_customer_info_frame').length > 0) {
1910            const $econtFrame = JqueryElementShipping.find('#econt_delivery_customer_info_frame');
1911            const $econtCarrierOption = $econtFrame.closest('.carrier-extra-content').prev('.delivery-option');
1912
1913            if ($econtCarrierOption.find('.delivery_option_radio').is(':checked') && !window._opcEcontDeliverySelected) {
1914                JqueryElement.data('opc-payment-toggleOrderButton-validation', false);
1915                $(confirmationAlertSelector).text(OPC.Message.pickupStoreRequired);
1916            }
1917        }
1918    });
1919
1920    // Cart - Events
1921    prestashop.on('opc-cart-getCartSummary-complete', (params) => {
1922        const { JqueryElement } = params;
1923
1924        // Actualiza el total del carrito del header si existe.
1925        if ($('.blockcart').length > 0) {
1926            const refreshURL = $('.blockcart').data('refresh-url');
1927
1928            if (refreshURL !== '') {
1929                $.post(refreshURL, {}).then(function (response) {
1930                    $('.blockcart').replaceWith($(response.preview).find('.blockcart'));
1931                });
1932            }
1933        }
1934
1935        // darique - v3.0.10 - Silbersaiten
1936        if (typeof dariqueModule !== typeof undefined) {
1937            dariqueModule.initLoaderBlock();
1938            dariqueModule.loadPresentsBlock(0, []);
1939        }
1940
1941        const carousels = document.querySelectorAll('.swiper-default-carousel');
1942        if (carousels.length > 0 && typeof Swiper !== typeof undefined) {
1943            const defaultOptions = {
1944                pagination: {
1945                    el: '.swiper-pagination-product',
1946                    clickable: true,
1947                },
1948                loop: false,
1949                speed: 600,
1950                lazy: {
1951                    loadedClass: 'loaded',
1952                    checkInView: true,
1953                },
1954                watchOverflow: true,
1955                watchSlidesProgress: true,
1956                watchSlidesVisibility: true,
1957                navigation: {
1958                    nextEl: '.swiper-button-next',
1959                    prevEl: '.swiper-button-prev',
1960                },
1961                slidesPerView: iqitTheme.pl_slider_p,
1962                slidesPerGroup: iqitTheme.pl_slider_p,
1963                breakpoints: {
1964                    768: {
1965                        slidesPerView: iqitTheme.pl_slider_t,
1966                        slidesPerGroup: iqitTheme.pl_slider_t,
1967                    },
1968                    992: {
1969                        slidesPerView: iqitTheme.pl_slider_d,
1970                        slidesPerGroup: iqitTheme.pl_slider_d,
1971                    },
1972                    1200: {
1973                        slidesPerView: iqitTheme.pl_slider_ld,
1974                        slidesPerGroup: iqitTheme.pl_slider_ld,
1975                    },
1976                },
1977            };
1978
1979            if (iqitTheme.pl_crsl_autoplay) {
1980                defaultOptions.autoplay = {
1981                    delay: 4500,
1982                    disableOnInteraction: true,
1983                };
1984            }
1985
1986            carousels.forEach(function(carousel) {
1987                let customOptions = {};
1988
1989                const dataOptions = carousel.getAttribute('data-swiper_options');
1990
1991                if (dataOptions) {
1992                    try {
1993                        customOptions = JSON.parse(dataOptions);
1994                    } catch (err) {
1995                        console.error('data swiper parse error', carousel, err);
1996                    }
1997                }
1998
1999                const swiperOptions = Object.assign({}, defaultOptions, customOptions);
2000                new Swiper(carousel, swiperOptions);
2001            });
2002        }
2003    });
2004}
2005
2006window.addEventListener("load", function(){
2007    // idxlopd - v3.12.2 - innovadeluxe
2008    // Insert necesario para el funcionamiento de la compatibilidad.
2009    // INSERT INTO `ps_idxlopd_compatible` (`id_button`, `name`, `text`, `controller`, `details`, `id_shop`) VALUES
2010    // ('btn-register', NULL, 'Crear cuenta', 'order', NULL, NULL),
2011    // ('btn-register-guest', NULL, 'Continuar como invitado', 'order', NULL, NULL);
2012    if (typeof makeButtonCompatible !== typeof undefined) {
2013        makeButtonCompatible();
2014    }
2015});

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.