diff --git a/ps_onepagecheckout.php b/ps_onepagecheckout.php index 69d5784..1000938 100644 --- a/ps_onepagecheckout.php +++ b/ps_onepagecheckout.php @@ -78,7 +78,8 @@ public function install() && $this->registerHook('actionCheckoutBuildProcess') && $this->registerHook('actionFrontControllerSetMedia') && $this->registerHook('actionFrontControllerSetVariables') - && $this->registerHook('actionModuleUpgradeAfter'); + && $this->registerHook('actionModuleUpgradeAfter') + && $this->registerHook('displayOverrideTemplate'); } public function enable($force_all = false) @@ -152,7 +153,7 @@ public function hookActionCheckoutBuildProcess(array $params = []): CheckoutProc public function hookActionFrontControllerSetMedia(): void { - if (!isset($this->context->controller) || $this->context->controller->php_self !== 'order') { + if (!isset($this->context->controller) || !$this->context->controller instanceof OrderController) { return; } @@ -292,7 +293,7 @@ public function hookActionFrontControllerSetMedia(): void public function hookActionFrontControllerSetVariables(array $params): void { - if (!isset($this->context->controller) || $this->context->controller->php_self !== 'order') { + if (!isset($this->context->controller) || !$this->context->controller instanceof OrderController) { return; } @@ -303,6 +304,19 @@ public function hookActionFrontControllerSetVariables(array $params): void $params['templateVars']['is_one_page_checkout_enabled'] = $this->isOnePageCheckoutEnabled(); } + public function hookDisplayOverrideTemplate(array $params) + { + if ( + $this->isOnePageCheckoutEnabled() + && $params['template_file'] === 'checkout/checkout' + && $params['controller'] instanceof OrderController + ) { + return 'module:ps_onepagecheckout/views/templates/front/checkout/checkout.tpl'; + } + + return null; + } + public function isOnePageCheckoutEnabled(): bool { return (new OnePageCheckoutAvailability(self::CONFIG_ONE_PAGE_CHECKOUT_ENABLED))->isEnabled(); diff --git a/src/Checkout/CheckoutOnePageStep.php b/src/Checkout/CheckoutOnePageStep.php index 6862e60..284dcea 100644 --- a/src/Checkout/CheckoutOnePageStep.php +++ b/src/Checkout/CheckoutOnePageStep.php @@ -277,6 +277,7 @@ public function render(array $extraParams = []) 'isGift' => $this->getCheckoutSession()->getGift()['isGift'], 'message' => $this->getCheckoutSession()->getGift()['message'], ], + 'opc_urls' => $this->getOpcUrls(), 'is_virtual_cart' => $this->context->cart->isVirtualCart(), 'configuration' => $this->getTemplateConfiguration(), ] + $this->opcForm->getTemplateVariables(); @@ -302,6 +303,24 @@ private function getSelectedPaymentSelectionKey(): string return (string) ($this->context->cookie->__get('opc_selected_payment_selection_key') ?: ''); } + /** + * @return array{authentication: string, registration: string} + */ + private function getOpcUrls(): array + { + $backParams = []; + $orderPageUrl = $this->context->link->getPageLink('order', true); + + if (is_string($orderPageUrl) && \Tools::urlBelongsToShop($orderPageUrl)) { + $backParams = ['back' => $orderPageUrl]; + } + + return [ + 'authentication' => $this->context->link->getPageLink('authentication', true, null, $backParams), + 'registration' => $this->context->link->getPageLink('registration', true, null, $backParams), + ]; + } + private function restoreLastFailedSubmitState(): void { $submitState = $this->submitValidationStateStorage->consume(); diff --git a/src/Form/OnePageCheckoutForm.php b/src/Form/OnePageCheckoutForm.php index bf7c290..ed8bed3 100644 --- a/src/Form/OnePageCheckoutForm.php +++ b/src/Form/OnePageCheckoutForm.php @@ -49,6 +49,9 @@ class OnePageCheckoutForm extends \AbstractForm */ protected $formatter; + /** + * @var \Context + */ private $context; private $customerPersister; private $addressPersister; diff --git a/tests/php/Unit/Checkout/CheckoutOnePageStepRenderTest.php b/tests/php/Unit/Checkout/CheckoutOnePageStepRenderTest.php index e0c181b..5345921 100644 --- a/tests/php/Unit/Checkout/CheckoutOnePageStepRenderTest.php +++ b/tests/php/Unit/Checkout/CheckoutOnePageStepRenderTest.php @@ -51,6 +51,18 @@ public function __get(string $name) return ''; } }; + $link = $this->createMock(\Link::class); + $link->method('getPageLink') + ->willReturnCallback(static function ($controller, $ssl = null, $idLang = null, $request = null, $requestUrlEncode = false, $idShop = null, $relativeProtocol = false): string { + $url = '/' . $controller; + + if (is_array($request) && $request !== []) { + $url .= '?' . http_build_query($request); + } + + return $url; + }); + $context->link = $link; $context->controller = new class { public function getTemplateVarConfiguration(): array { @@ -186,6 +198,8 @@ protected function renderTemplate($template, array $extraParams = [], array $par self::assertArrayHasKey('deliveryFields', $step->capturedParams); self::assertArrayHasKey('invoiceFields', $step->capturedParams); self::assertArrayHasKey('errors', $step->capturedParams); + self::assertSame('/authentication?back=%2Forder', $step->capturedParams['opc_urls']['authentication']); + self::assertSame('/registration?back=%2Forder', $step->capturedParams['opc_urls']['registration']); self::assertArrayHasKey('configuration', $step->capturedParams); self::assertArrayHasKey('is_guest_checkout_enabled', $step->capturedParams['configuration']); self::assertIsBool($step->capturedParams['configuration']['is_guest_checkout_enabled']); diff --git a/tests/php/Unit/Js/OpcJavascriptContractTest.php b/tests/php/Unit/Js/OpcJavascriptContractTest.php deleted file mode 100644 index 1e4f35a..0000000 --- a/tests/php/Unit/Js/OpcJavascriptContractTest.php +++ /dev/null @@ -1,100 +0,0 @@ -registerHookCalls); } @@ -122,7 +123,7 @@ public function testHookActionFrontControllerSetVariablesInjectsRuntimeFlagOnOrd $module = $this->createModule(); $module->isEnabled = true; $module->setModuleContext((object) [ - 'controller' => (object) ['php_self' => 'order'], + 'controller' => new DummyOrderController(), ]); $templateVars = []; @@ -139,7 +140,7 @@ public function testHookActionFrontControllerSetMediaAssignsFlagAndRegistersAsse $module->name = 'ps_onepagecheckout'; $module->isEnabled = true; $module->setModuleContext((object) [ - 'controller' => (object) ['php_self' => 'order'], + 'controller' => new DummyOrderController(), 'smarty' => new DummySmarty(), 'link' => new DummyLink(), ]); @@ -151,6 +152,7 @@ public function testHookActionFrontControllerSetMediaAssignsFlagAndRegistersAsse self::assertTrue($smarty->assigned['is_one_page_checkout_enabled']); self::assertCount(1, $module->javascriptDefinitions); self::assertSame(1, $module->registeredJavascriptAssetsCalls); + self::assertSame(1, $module->registeredStylesheetAssetsCalls); self::assertArrayHasKey('ps_onepagecheckout', $module->javascriptDefinitions[0]); self::assertNotEmpty($module->javascriptDefinitions[0]['ps_onepagecheckout']['urls']['guestInit']); self::assertNotEmpty($module->javascriptDefinitions[0]['ps_onepagecheckout']['urls']['addressForm']); @@ -171,7 +173,7 @@ public function testHookActionFrontControllerSetMediaAssignsFlagAndSkipsAssetsWh $module->name = 'ps_onepagecheckout'; $module->isEnabled = false; $module->setModuleContext((object) [ - 'controller' => (object) ['php_self' => 'order'], + 'controller' => new DummyOrderController(), 'smarty' => new DummySmarty(), 'link' => new DummyLink(), ]); @@ -183,6 +185,7 @@ public function testHookActionFrontControllerSetMediaAssignsFlagAndSkipsAssetsWh self::assertFalse($smarty->assigned['is_one_page_checkout_enabled']); self::assertCount(0, $module->javascriptDefinitions); self::assertSame(0, $module->registeredJavascriptAssetsCalls); + self::assertSame(0, $module->registeredStylesheetAssetsCalls); } public function testMainModuleFileDoesNotContainCustomAutoloaderRegistration(): void @@ -228,6 +231,7 @@ class TestablePsOnepagecheckoutModule extends \Ps_Onepagecheckout public array $javascriptDefinitions = []; public int $registeredJavascriptAssetsCalls = 0; + public int $registeredStylesheetAssetsCalls = 0; public bool $isEnabled = true; public int $disableCurrentContextCalls = 0; public bool $disableCurrentContextResult = true; @@ -294,6 +298,11 @@ protected function registerOpcJavascriptAssets(): void ++$this->registeredJavascriptAssetsCalls; } + protected function registerOpcStylesheets(): void + { + ++$this->registeredStylesheetAssetsCalls; + } + protected function disableInParent(bool $forceAll): bool { $this->disableInParentCalls[] = $forceAll; @@ -339,6 +348,13 @@ public function assign($key, $value = null): void } } +class DummyOrderController extends \OrderController +{ + public function __construct() + { + } +} + class DummyLink { /** diff --git a/views/js/events.js b/views/js/events.js index 8cc7e25..448cb55 100644 --- a/views/js/events.js +++ b/views/js/events.js @@ -5,6 +5,7 @@ const OPC_EVENTS = { opcCarriersLoading: 'opcCarriersLoading', opcPaymentMethodsLoading: 'opcPaymentMethodsLoading', opcPaymentMethodsUpdated: 'opcPaymentMethodsUpdated', + opcPaymentMethodsRefreshed: 'opcPaymentMethodsRefreshed', opcPaymentMethodsFailed: 'opcPaymentMethodsFailed', opcPaymentMethodSelected: 'opcPaymentMethodSelected', opcGuestInitSuccess: 'opcGuestInitSuccess', diff --git a/views/js/opc-address-modal.js b/views/js/opc-address-modal.js index d10eea0..6e5f3f4 100644 --- a/views/js/opc-address-modal.js +++ b/views/js/opc-address-modal.js @@ -454,7 +454,7 @@ function updateStateFieldUi($modal, response, selectedStateId) { $wrapper.hide(); $select.prop('required', false).val(''); if ($row.length) { - $row.removeClass('form-fields-row--3').addClass('form-fields-row--2'); + $row.removeClass('opc-form-fields-row--3').addClass('opc-form-fields-row--2'); } return; @@ -476,7 +476,7 @@ function updateStateFieldUi($modal, response, selectedStateId) { $wrapper.show(); $select.prop('required', true); if ($row.length) { - $row.removeClass('form-fields-row--2').addClass('form-fields-row--3'); + $row.removeClass('opc-form-fields-row--2').addClass('opc-form-fields-row--3'); } } diff --git a/views/js/opc-carrier-list.js b/views/js/opc-carrier-list.js index c42da77..bc9eb09 100644 --- a/views/js/opc-carrier-list.js +++ b/views/js/opc-carrier-list.js @@ -133,10 +133,12 @@ function fetchCarriers() { const checkedCarrier = $container.find(`${OPC_SELECTORS.inputs.deliveryOption}:checked`).get(0); if (checkedCarrier) { - $container.attr('data-confirmed-delivery-option', String(checkedCarrier.value || '')); + const selectedDeliveryOption = String(checkedCarrier.value || ''); + + $container.attr('data-confirmed-delivery-option', selectedDeliveryOption); prestashop.emit(OPC_EVENTS.opcCarrierSelected, { - carrierId: checkedCarrier.value, - resp: response, + selectedDeliveryOption, + response, }); } else { $container.removeAttr('data-confirmed-delivery-option'); diff --git a/views/js/opc-carrier-select.js b/views/js/opc-carrier-select.js index c40de7d..b796beb 100644 --- a/views/js/opc-carrier-select.js +++ b/views/js/opc-carrier-select.js @@ -17,7 +17,9 @@ if (!$) { const CONTAINER_SELECTOR = OPC_SELECTORS.opc.deliveryMethods; const URL_KEY = 'selectCarrier'; const CHECKOUT_FORM_SELECTOR = OPC_SELECTORS.opc.checkout; +const OPC_FORM_SELECTOR = OPC_SELECTORS.opc.form; const DELIVERY_ADDRESS_SECTION_SELECTOR = OPC_SELECTORS.opc.deliverySection; +const DELIVERY_OPTION_SELECTOR = '.js-delivery-option'; const CONFIRMED_DELIVERY_OPTION_ATTRIBUTE = 'data-confirmed-delivery-option'; function getDeliveryAddressSection() { @@ -113,7 +115,16 @@ $(document).on('change', `${CONTAINER_SELECTOR} ${OPC_SELECTORS.inputs.deliveryO updateCartSummary(response.preview, response.totals); } setConfirmedDeliveryOption($container, deliveryOption); - prestashop.emit(OPC_EVENTS.opcCarrierSelected, response); + // Keep existing themes compatible with the 4-step checkout carrier lifecycle. + prestashop.emit('updatedDeliveryForm', { + dataForm: $(OPC_FORM_SELECTOR).serializeArray(), + deliveryOption: $radio.closest(DELIVERY_OPTION_SELECTOR), + resp: response, + }); + prestashop.emit(OPC_EVENTS.opcCarrierSelected, { + selectedDeliveryOption: deliveryOption, + response, + }); }) .fail((jqXHR) => { restoreConfirmedDeliveryOption($container); diff --git a/views/js/opc-payment-list.js b/views/js/opc-payment-list.js index d438b13..a6b9dda 100644 --- a/views/js/opc-payment-list.js +++ b/views/js/opc-payment-list.js @@ -18,6 +18,7 @@ const CONTAINER_SELECTOR = OPC_SELECTORS.opc.paymentMethods; const CHECKOUT_FORM_SELECTOR = OPC_SELECTORS.opc.checkout; const URL_KEY = 'paymentMethods'; let fetchGeneration = 0; +let lastFetchedPaymentListDom = null; function getTemplateHtml(templateId) { const template = document.querySelector(`#${templateId}`); @@ -46,16 +47,29 @@ function getSelectedSavedAddressId(listSelector, radioName) { return selectedAddressId; } -function setLoading() { - const $container = getContainer(); - if (!$container.length) { +function getLoaderOverlay() { + return document.getElementById('opc-payment-methods-loader'); +} + +function showLoader() { + const overlay = getLoaderOverlay(); + if (!overlay) { return; } - - $container.html(getTemplateHtml(OPC_SELECTORS.templates.paymentLoader.replace('#', ''))); + overlay.classList.remove('d-none'); + overlay.setAttribute('aria-hidden', 'false'); prestashop.emit(OPC_EVENTS.opcPaymentMethodsLoading, {}); } +function hideLoader() { + const overlay = getLoaderOverlay(); + if (!overlay) { + return; + } + overlay.classList.add('d-none'); + overlay.setAttribute('aria-hidden', 'true'); +} + function buildPaymentMethodsUrl(baseUrl) { const form = getCheckoutForm(); @@ -103,7 +117,7 @@ function fetchPaymentMethods() { } const generation = ++fetchGeneration; - setLoading(); + showLoader(); $.get(paymentMethodsUrl) .done((response) => { @@ -114,14 +128,25 @@ function fetchPaymentMethods() { if (!response || response.success === false) { const error = normalizeErrorResponse(response, 'Unable to load payment methods.'); $container.html(getTemplateHtml(OPC_SELECTORS.templates.paymentError.replace('#', ''))); + lastFetchedPaymentListDom = null; + hideLoader(); prestashop.emit(OPC_EVENTS.opcPaymentMethodsFailed, {error}); prestashop.emit('handleError', {eventType: 'opcPaymentMethods', resp: error}); return; } - $container.html(response.payment_html || ''); - prestashop.emit(OPC_EVENTS.opcPaymentMethodsUpdated, response); + const responsePaymentHtml = response.payment_html || ''; + + if (lastFetchedPaymentListDom !== responsePaymentHtml) { + $container.html(responsePaymentHtml); + lastFetchedPaymentListDom = responsePaymentHtml; + prestashop.emit(OPC_EVENTS.opcPaymentMethodsUpdated, response); + } else { + prestashop.emit(OPC_EVENTS.opcPaymentMethodsRefreshed, response); + } + + hideLoader(); }) .fail((jqXHR) => { if (generation !== fetchGeneration) { @@ -130,6 +155,8 @@ function fetchPaymentMethods() { const error = getAjaxErrorResponse(jqXHR, 'Unable to load payment methods.'); $container.html(getTemplateHtml(OPC_SELECTORS.templates.paymentError.replace('#', ''))); + lastFetchedPaymentListDom = null; + hideLoader(); prestashop.emit(OPC_EVENTS.opcPaymentMethodsFailed, {error}); prestashop.emit('handleError', {eventType: 'opcPaymentMethods', resp: error}); }); @@ -147,7 +174,7 @@ prestashop.on(OPC_EVENTS.opcGuestInitSuccess, fetchPaymentMethods); prestashop.on(OPC_EVENTS.opcPaymentMethodsRetry, fetchPaymentMethods); prestashop.on(OPC_EVENTS.opcCarriersLoading, () => { fetchGeneration += 1; - setLoading(); + showLoader(); }); prestashop.on(OPC_EVENTS.opcCarriersFailed, () => { @@ -156,7 +183,9 @@ prestashop.on(OPC_EVENTS.opcCarriersFailed, () => { fetchGeneration += 1; if ($container.length) { $container.html(getTemplateHtml(OPC_SELECTORS.templates.paymentError.replace('#', ''))); + lastFetchedPaymentListDom = null; } + hideLoader(); prestashop.emit(OPC_EVENTS.opcPaymentMethodsFailed, {error: 'carrier fetch failed'}); }); diff --git a/views/js/opc-submit.js b/views/js/opc-submit.js index 7919cff..e032a7e 100644 --- a/views/js/opc-submit.js +++ b/views/js/opc-submit.js @@ -453,6 +453,47 @@ async function continueSuccessfulSubmit(response, paymentRadio) { window.location.href = response.checkout_url || window.prestashop.urls.pages.order; } + async function submitBeforePayment(form) { + if (isFinalSubmitInFlight) { + return; + } + + const paymentSelection = ensureSubmitPreconditions(form); + if (!paymentSelection.isValid) { + return; + } + + const submitUrl = getOpcSubmitUrl(); + if (!submitUrl) { + return; + } + + const payButton = getPayButton(); + const payload = buildSubmitPayload(form, paymentSelection.paymentRadio); + + isFinalSubmitInFlight = true; + if (payButton instanceof HTMLButtonElement) { + payButton.disabled = true; + } + + try { + const response = await fetchOpcSubmitResponse(submitUrl, payload); + + if (!response || response.success !== true) { + handleOpcSubmitFailure(response); + } + } catch (error) { + prestashop.emit('handleError', { + eventType: 'opcSubmit', + resp: {}, + }); + prestashop.emit(OPC_EVENTS.opcSubmitFailed, {error}); + } finally { + isFinalSubmitInFlight = false; + validateForm(); + } + } + async function submitOpcPay(form) { if (isFinalSubmitInFlight) { return; @@ -519,6 +560,7 @@ function bindValidationListeners(form, payButton) { if (!payButton.dataset.opcSubmitHandlerBound) { payButton.addEventListener('click', (event) => { event.preventDefault(); + event.stopPropagation(); if (isFinalSubmitInFlight || payButton.disabled) { return; @@ -595,6 +637,16 @@ $(document).ready(() => { initBillingToggle(); validateForm(); + window.ps_onepagecheckout.submitBeforePayment = () => { + const form = getCheckoutForm(); + + if (!(form instanceof HTMLFormElement)) { + return Promise.reject(new Error('OPC form not found.')); + } + + return submitBeforePayment(form); + }; + prestashop.on(OPC_EVENTS.opcPaymentMethodsLoading, () => { paymentMethodsState = 'loading'; validateForm(); @@ -609,6 +661,11 @@ $(document).ready(() => { bindScopedValidationListeners(form); validateForm(); }); + prestashop.on(OPC_EVENTS.opcPaymentMethodsRefreshed, () => { + paymentMethodsState = 'ready'; + bindScopedValidationListeners(form); + validateForm(); + }); prestashop.on(OPC_EVENTS.opcDeliveryAddressUpdated, () => { initBillingToggle(); bindScopedValidationListeners(form); diff --git a/views/js/selectors.js b/views/js/selectors.js index 08e64fd..256d956 100644 --- a/views/js/selectors.js +++ b/views/js/selectors.js @@ -23,7 +23,6 @@ const OPC_SELECTORS = { templates: { carrierLoader: '#opc-template-loader', carrierError: '#opc-template-carriers-error', - paymentLoader: '#opc-template-payment-loader', paymentError: '#opc-template-payment-error', }, inputs: { diff --git a/views/public/one-page-checkout.css b/views/public/one-page-checkout.css index a581d4f..688821a 100644 --- a/views/public/one-page-checkout.css +++ b/views/public/one-page-checkout.css @@ -1 +1 @@ -@media(min-width: 992px){.one-page-checkout{padding-right:2em}}.one-page-checkout__section{padding-top:1em;padding-bottom:1em}.one-page-checkout__section+.one-page-checkout__section,.js-opc-addresses-section+.one-page-checkout__section{padding-top:2em;border-top:1px solid var(--bs-border-color)}.one-page-checkout__links{display:flex;flex-wrap:wrap-reverse;gap:.5rem;justify-content:space-between;margin-bottom:1.5rem}.one-page-checkout__field{margin-bottom:1rem}.one-page-checkout__footer{padding-top:2em}.one-page-checkout__footer .form-check{margin-bottom:1rem}.one-page-checkout .opc-address-item .form-check-input,.one-page-checkout .opc-address-item .form-check-label{cursor:pointer} +.opc-form-fields-row{display:grid;gap:1rem}@media(min-width: 768px){.opc-form-fields-row--2{grid-template-columns:1fr 1fr}.opc-form-fields-row--3{grid-template-columns:1fr 1fr 1fr}}@media(min-width: 992px){.one-page-checkout{padding-right:2em}}.one-page-checkout__section{padding-top:1em;padding-bottom:1em}.one-page-checkout__section+.one-page-checkout__section,.js-opc-addresses-section+.one-page-checkout__section{padding-top:2em;border-top:1px solid var(--bs-border-color)}.one-page-checkout__links{display:flex;flex-wrap:wrap-reverse;gap:.5rem;justify-content:space-between;margin-bottom:1.5rem}.one-page-checkout__field{margin-bottom:1rem}.one-page-checkout__footer{padding-top:2em}.one-page-checkout__footer .form-check{margin-bottom:1rem}.one-page-checkout .opc-address-item .form-check-input,.one-page-checkout .opc-address-item .form-check-label{cursor:pointer}.opc-payment-methods-wrapper{min-height:6rem} diff --git a/views/public/opc-address-modal.bundle.js b/views/public/opc-address-modal.bundle.js index da88340..2b97dcc 100644 --- a/views/public/opc-address-modal.bundle.js +++ b/views/public/opc-address-modal.bundle.js @@ -1 +1 @@ -(()=>{"use strict";const e={opcCarrierSelected:"opcCarrierSelected",opcCarriersUpdated:"opcCarriersUpdated",opcCarriersFailed:"opcCarriersFailed",opcCarriersLoading:"opcCarriersLoading",opcPaymentMethodsLoading:"opcPaymentMethodsLoading",opcPaymentMethodsUpdated:"opcPaymentMethodsUpdated",opcPaymentMethodsFailed:"opcPaymentMethodsFailed",opcPaymentMethodSelected:"opcPaymentMethodSelected",opcGuestInitSuccess:"opcGuestInitSuccess",opcFinalSubmitStarted:"opcFinalSubmitStarted",opcFormValidated:"opcFormValidated",opcBillingSectionToggled:"opcBillingSectionToggled",opcSubmitFailed:"opcSubmitFailed",opcDeliveryAddressUpdated:"opcDeliveryAddressUpdated",opcBillingAddressUpdated:"opcBillingAddressUpdated",opcDeliveryAddressSelected:"opcDeliveryAddressSelected",opcBillingAddressSelected:"opcBillingAddressSelected",opcCartSummaryBeforeUpdate:"opcCartSummaryBeforeUpdate",opcCartSummaryUpdated:"opcCartSummaryUpdated",updatedOpcAddressForm:"updatedOpcAddressForm",opcCarriersRetry:"opcCarriersRetry",opcPaymentMethodsRetry:"opcPaymentMethodsRetry"};const t={opc:{checkout:".one-page-checkout",form:"#opc-form",payButton:"#opc-pay-button",payAmount:"#opc-pay-amount",addressesSection:".js-opc-addresses-section",deliveryMethods:"#opc-delivery-methods",paymentMethods:"#opc-payment-methods",deliverySection:"#opc-delivery-address",deliveryFields:"#opc-delivery-address-fields",billingSection:"#opc-billing-section",billingFields:"#opc-billing-address-fields",deliveryList:"#opc-delivery-address-content-list",billingList:"#opc-billing-address-content-list",useSameAddress:'[name="use_same_address"]',checkoutFooter:".one-page-checkout__footer",contactSection:".js-opc-contact-section",addressRadio:".js-opc-address-radio",addressItem:".opc-address-item",addressLabel:".form-check-label"},templates:{carrierLoader:"#opc-template-loader",carrierError:"#opc-template-carriers-error",paymentLoader:"#opc-template-payment-loader",paymentError:"#opc-template-payment-error"},inputs:{deliveryOption:'input[name="delivery_option"]',paymentOption:'input[name="payment-option"]',email:'input[name="email"]',conditions:'.one-page-checkout input[name^="conditions_to_approve["][required]'},modals:{address:"#opc-address-modal, #modal-delivery, #modal-invoice"}};function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}function n(e){var t=window&&"object"===r(window.ps_onepagecheckout)&&window.ps_onepagecheckout?window.ps_onepagecheckout:null;return t&&t.urls&&t.urls[e]?String(t.urls[e]):""}function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var n,o,i,d,a=[],s=!0,c=!1;try{if(i=(r=r.call(e)).next,0===t){if(Object(r)!==r)return;s=!1}else for(;!(s=(n=i.call(r)).done)&&(a.push(n.value),a.length!==t);s=!0);}catch(e){c=!0,o=e}finally{try{if(!s&&null!=r.return&&(d=r.return(),Object(d)!==d))return}finally{if(c)throw o}}return a}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?i(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r0}(e))||e.checkValidity())}function K(e){var t=e.find(s);if(t.length)if(e.get(0)instanceof HTMLElement&&e.hasClass("show")){var r=e.find("input, select, textarea").toArray().every(function(e){return Q(e)});t.prop("disabled",!r)}else t.prop("disabled",!0)}function W(e,t,n){var o=function(e){return{$wrapper:e.find(".state-field-wrapper, #state-field-wrapper").first(),$select:e.find('[name="id_state"], [name$="id_state"]').first(),$row:e.find(".address-country-row, #address-country-row").first()}}(e),i=o.$wrapper,d=o.$select,a=o.$row;if(i.length&&d.length){var s=t&&Array.isArray(t.states)?t.states:[];if(!(Boolean(t&&t.hasStates)||s.length>0))return i.hide(),d.prop("required",!1).val(""),void(a.length&&a.removeClass("form-fields-row--3").addClass("form-fields-row--2"));var c=String(d.attr("data-select-placeholder")||"");d.empty(),d.append(r("