Informação de meios de pagamento e parcelas

Neste tutorial, vamos adicionar visibilidade aos meios de pagamento e parcelas em seu design:

O código neste tutorial inclui:

  • Parcelas na lista de produtos, detalhes do produto e carrinho (aplica-se apenas em lojas no Brasil)
  • Modal que mostra o detalhe dos meios de pagamento oferecidos pela loja

HTML

1. A primeira coisa que vamos fazer é chamar o componente parcelas para mostrar na lista de produtos.

No arquivo item.tpl vamos adicionar a seguinte chamada

{{ component('installments', {'location' : 'product_item', container_classes: { installment: "item-installments"}}) }} 

Este componente inclui a mensagem de parcelas e permite os seguintes parâmetros:

location:

  • cart: Vamos usá-lo incluindo-o no carrinho
  • product_item: Vamos usá-lo incluindo-o no item da lista de produtos
  • product_detail: Vamos utilizá-lo incluindo-o nos detalhes do produto, por exemplo, abaixo do preço

container_classes.installment: Usado para passar classes para o contêiner geral da mensagem de parcelas

Deve quedar algo semelhante a isto:

2. Do mesmo jeito que fizemos para o item da lista de produtos, agora faremos para a mensagem de parcelas no carrinho de compras. Vamos procurar o tpl cart-totals.tpl ou onde temos o total do carrinho, e logo abaixo vamos adicionar o seguinte código:

{{ component('installments', {'location': 'cart', container_classes: { installment: "mt-1 font-weight-bold text-right"}}) }}

Vamos lembrar os parâmetros que usamos para o item produto, mas neste caso usamos "location" : "cart" e as classes que precisamos para o CSS, deixando algo como o exemplo a seguir:

3.  Deixamos o detalhe do produto para o final, pois teremos 2 partes: a mensagem de cota abaixo do preço e o popup que mostra o detalhe do meio de pagamento.

A primeira coisa que vamos fazer é entender onde temos o formulário do produto, por exemplo pode ser o tpl product-form.tpl e abaixo do preço incluir o componente de parcelas como fizemos antes (não esqueçamos de colocar o "localização "parâmetro corretamente)

{{ component('installments', {'location' : 'product_detail', container_classes: { installment: "product-detail-installments text-center text-md-left mb-2" }}) }}

Deixando algo como o seguinte:

Por fim, para o popup de meios de pagamento, adicionamos o arquivo product-payment-details.tpl dentro da pasta product, com o seguinte código.

{# Product payments details #}

{% if product.installments_info_from_any_variant %}

    {% embed "snipplets/modal.tpl" with{
        modal_id: 'installments-modal', 
        modal_position: 'bottom', 
        modal_transition: 'slide', 
        modal_header: true, 
        modal_footer: true, 
        modal_width: 'centered', 
        modal_mobile_full_screen: 'true'} %}
        {% block modal_head %}
            {{ 'Medios de pago' | translate }}
        {% endblock %}
        {% block modal_body %}

            {# Modal header and gateways tab links #}

            {{ component('payments/payments-details',
                {
                    text_classes: {
                        text_accent: "label label-accent ml-1",
                        subtitles: "mb-3",
                        text_big: "font-big",
                        text_small: "font-small",
                        align_right: "text-right"
                    },
                    spacing_classes: {
                        top_1x: "mt-1",
                        top_2x: "mt-2",
                        top_3x: "mt-3",
                        right_1x: "mr-1",
                        right_2x: "mr-2",
                        right_3x: "mr-3",
                        bottom_1x: "mb-1",
                        bottom_2x: "mb-2",
                        bottom_3x: "mb-3",
                        left_3x: "ml-3",
                    },
                    container_classes : {
                        payment_method: "card p-3"
                    }
                })
            }}
        {% endblock %}
        {% block modal_foot %}
            <div class="text-right">
                <span class="js-modal-close js-fullscreen-modal-close btn-link pull-right">{{ 'Volver al producto' | translate }}</span>
            </div>
        {% endblock %}
    {% endembed %}
{% endif %}

Os parâmetros que o componente de detalhes de pagamentos aceita são principalmente classes para CSS

Classes para textos:

  • text_classes.text_accent: Usado para os textos de destaque, como descontos
  • text_classes.subtitles: Usado para os subtítulos dentro do pop-up
  • text_classes.text_big: Usado para textos grandes que ainda é menor que um título
  • text_classes.text_small: Usado para textos pequenos

Classes de espaçamento:

  • spacing_classes.top_1x: Usado para margens superiores em 1x
  • spacing_classes.right_1x: Usado para margens à direita em 1x
  • spacing_classes.right_2x: Usado para margens à direita em 2x
  • spacing_classes.right_3x: Usado para margens à direita em 3x
  • spacing_classes.bottom_1x: Usado para margens inferiores em 1x
  • spacing_classes.bottom_2x: Usado para margens inferiores em 2x
  • spacing_classes.bottom_3x: Usado para margens inferiores em 3x
  • spacing_classes.left_3x:  Usado para margens à esquerda em 3x

E o incluímos no final do formulário do produto no arquivo product-form.tpl

{# Product payments details #}

{% include 'snipplets/product/product-payment-details.tpl' %}

Este pop-up inclui o detalhe de todos os meios de pagamento bem como os descontos que cada um pode oferecer.

Antes de prosseguir não devemos esquecer de adicionar o link que abre o pop-up. Podemos adicioná-lo onde precisarmos:

<a id="btn-installments" class="btn-link" {% if not (product.get_max_installments and product.get_max_installments(false)) %}style="display: none;"{% endif %}>
    {{ "Ver medios de pago" | translate }}
</a>

4. Teremos que adicionar alguns IDs e classes nos detalhes do produto para que as cotas sejam atualizadas ao alterar a variante. No layout Base, modificamos o template product.tpl e o snipplet product-form.tpl; mas no seu caso, você só precisa modificar product.tpl.

product.tpl

No div pai que abrange todo o conteúdo do detalhe do produto, inclua os seguintes IDs e seletores "js -..."

<div id="single-product" class=”js-product-detail js-product-container" data-variants="{{product.variants_object | json_encode }}" itemscope itemtype="http://schema.org/Product">

product-form.tpl

Neste arquivo iremos substituir o código que você tem em relação ao preço do produto com o seguinte (lembre-se que esta alteração se aplica neste snipplet mas no seu caso você pode aplicá-lo onde quer que você tenha o preço do produto)

{# Product price #}


<div class="price-container text-center text-sm-left" itemprop="offers" itemscope itemtype="http://schema.org/Offer">
    <span class="d-inline-block">
       <h4 id="compare_price_display" class="js-compare-price-display price-compare {% if product_can_show_installments or (product.promotional_offer and not product.promotional_offer.script.is_percentage_off) %}mb-2{% endif %}" {% if not product.compare_at_price or not product.display_price %}style="display:none;"{% else %} style="display:block;"{% endif %}>{% if product.compare_at_price and product.display_price %}{{ product.compare_at_price | money }}{% endif %}</h4>
    </span>
    <spa class="d-inline-block">
        <h4 class="js-price-display {% if product_can_show_installments or (product.promotional_offer and not product.promotional_offer.script.is_percentage_off) %}mb-2{% endif %}" id="price_display" itemprop="price"{% if product.display_price %} content="{{ product.price / 100 }}"{% endif %} {% if not product.display_price %}style="display:none;"{% endif %}>{% if product.display_price %}{{ product.price | money }}{% endif %}</h4>
    </span>
    <meta itemprop="priceCurrency" content="{{ product.currency }}" />
    {% if product.stock_control %}
        <meta itemprop="inventoryLevel" content="{{ product.stock }}" />
        <meta itemprop="availability" href="http://schema.org/{{ product.stock ? 'InStock' : 'OutOfStock' }}" content="{{ product.stock ? 'In stock' : 'Out of stock' }}" />
    {% endif %}
</div>

Também precisamos adicionar a classe js-addtocart ao botão “Comprar”, assim:

{% set state = store.is_catalog ? 'catalog' : (product.available ? product.display_price ? 'cart' : 'contact' : 'nostock') %}
{% set texts = {'cart': "Agregar al carrito", 'contact': "Consultar precio", 'nostock': "Sin stock", 'catalog': "Consultar"} %}
<input type="submit" class="js-addtocart js-prod-submit-form btn btn-primary btn-block mb-4 {{ state }}" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} />

5. Agora precisamos criar o snipplet para o componente modal ou pop-up dentro da pasta snipplets. Este tpl é chamado modal.tpl e o código é:

{# /*============================================================================
  #Modal
==============================================================================*/

#Properties
    // ID
    // Position - Top, Right, Bottom, Left
    // Transition - Slide and Fade
    // Width - Full and Box
    // modal_form_action - For modals that has a form


#Head
    // Block - modal_head
#Body
    // Block - modal_body
#Footer
    // Block - modal_footer

#}


{% set modal_overlay = modal_overlay | default(true) %}


<div id="{{ modal_id }}" class="js-modal modal modal-{{ modal_class }} modal-{{modal_position}} transition-{{modal_transition}} modal-{{modal_width}} transition-soft" style="display: none;">
    {% if modal_form_action %}
    <form action="{{ modal_form_action }}" method="post" class="{{ modal_form_class }}">
    {% endif %}
    <div class="js-modal-close modal-header">
        <span class="modal-close">
            {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
        </span>
        {% block modal_head %}{% endblock %}
    </div>
    <div class="modal-body">
        {% block modal_body %}{% endblock %}
    </div>
    {% if modal_footer %}
        <div class="modal-footer d-md-block">
            {% block modal_foot %}{% endblock %}
        </div>
    {% endif %}
    {% if modal_form_action %}
    </form>
    {% endif %}
</div>

6. Adicionamos a classe js-variation-option no select para variantes de detalhes do produto. No layout Base, isso está no tpl product-variants.tpl dentro da pasta snipplets/product. Em seu design, esse arquivo pode ser chamado de variants.tpl ou você deve aplicar a alteração diretamente no modelo product.tpl

7. Finalmente, para a parte HTML, precisamos adicionar uma pasta SVG dentro da pasta snipplets. Aqui vamos adicionar os SVGs para o ícone do cartão de crédito no detalhe do produto com o nome credit-card-blank.tpl, e o ícone para fechar o popup com o nome times.tpl

credit-card-blank.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M527.9 32H48.1C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48.1 48h479.8c26.6 0 48.1-21.5 48.1-48V80c0-26.5-21.5-48-48.1-48zm-6 400H54.1c-3.3 0-6-2.7-6-6V86c0-3.3 2.7-6 6-6h467.8c3.3 0 6 2.7 6 6v340c0 3.3-2.7 6-6 6zM192 364v8c0 6.6-5.4 12-12 12h-72c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h72c6.6 0 12 5.4 12 12zm192 0v8c0 6.6-5.4 12-12 12H236c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h136c6.6 0 12 5.4 12 12z"/></svg>

times.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>

CSS

Requisito:

Ter adicionado helper classes em seu layout. Você pode seguir este pequeno tutorial para fazer isso (é só copiar e colar algumas classes, não leva mais que 1 minuto).

1. Adicionamos o seguinte SASS de cores em style-colors.scss.tpl (ou na stylesheet do seu layout que possui as cores e fontes da loja). Lembre-se de que as variáveis de cores e fontes podem variar em relação ao seu layout:

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


/* // Wrappers */


.box{
  float: left;
  width: 100%;
  margin-bottom: 20px;
  padding:8px;
  border:1px solid rgba($main-foreground, .2);
}


/* // Dividers */ 


.divider{
  margin-top: 20px;
  margin-bottom: 20px;
  clear: both;
  border-bottom: 1px solid rgba($main-foreground, .1);
}


/* // Modals */


.modal{
  color: $main-foreground;
  background-color:$main-background;
}




/* // Links */


.btn-link{
  color: $primary-color;
  fill: $primary-color;
  text-transform: uppercase;
  border-bottom: 1px solid;
  font-weight: bold;
  cursor: pointer;
  &:hover,
  &:focus{
    color: rgba($primary-color, .5);
    fill: rgba($primary-color, .5);
  }
}


/* // Tables */ 


.table{
  background-color: $main-background;
  color: $main-foreground;
  tbody{
    tr:nth-child(odd){
      background-color: rgba($main-foreground, .05);
    }
  }
  th{
    padding: 8px;
    text-align: left;
  }
}


/* // Tabs */


.tab-group{
  border-bottom: 1px solid rgba($main-foreground, .1);
  .tab{
    &-link{
      color: $main-foreground;
    }
    &.active{
      .tab-link{
        border-bottom: 2px solid rgba($primary-color, .5);
        color: $primary-color;
      }
    }
  }
}

2. Adicione os estilos no arquivo static/style-critical.tpl

Se em seu layout você usar um stylesheet para o CSS crítico, precisaremos do seguinte código dentro dele, do contrário, você pode unificar o CSS dos passos 2 e 3 em um único arquivo.

/* // Images */


.card-img{
  margin: 0 5px 5px 0;
  border: 1px solid #00000012;
}
.card-img-small{
  height: 25px;
}
.card-img-medium{
  height: 35px;
}
.card-img-big{
  height: 50px;
}

3. Adicione os estilos no arquivo static/style-async.tpl

Se em seu layout você usar um stylesheet CSS assíncrono, precisaremos do seguinte código dentro dela, do contrário, você pode unificar o CSS dos passos 2 e 3 em um único arquivo.

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


/* // Modals */


.modal {
  position: fixed;
  top: 0;
  display: block;
  width: 80%;
  height: 100%;
  padding: 10px;
  -webkit-overflow-scrolling: touch;
  overflow-y: auto;
  transition: all .2s cubic-bezier(.16,.68,.43,.99);
  z-index: 20000;
  &-header{
    width: calc(100% + 20px);
    margin: -10px 0 10px -10px;
    padding: 10px 15px;
    font-size: 20px;
  }
  &-footer{
    padding: 10px;
    clear: both;
  }
  &-full {
    width: 100%;
  }
  &-docked-sm{
    width: 100%;
  }
  &-docked-small{
    width: 80%;
  }
  &-top{
    top: -100%;
    left: 0;
  }
  &-bottom{
    top: 100%;
    left: 0;
  }
  &-left{
    left: -100%;
  }
  &-right{
    right: -100%;
  }
  &-centered{
    height: 100%;
    width: 100%;
  }
  &-top.modal-show,
  &-bottom.modal-show {
    top: 0;
  }
  &-left.modal-show {
    left: 0;
  }
  &-right.modal-show {
    right: 0;
  }
  &-close { 
    display: inline-block;
    padding: 1px 5px 5px 0;
    margin-right: 5px;
    vertical-align: middle;
    cursor: pointer;
  }
  .tab-group{
    margin:  0 -10px 20px -10px;
  }
}

.modal-overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #00000047;
  z-index: 10000;
}


/* // Tables */


.table{
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
  thead{
    th{
      padding: 8px;
      &:first-of-type{
        padding-left: 0;
      }
    }
  }
  td{
    padding: 8px;
    text-align: left;
  }
}

/* // Tabs */

.tab-group{
  width: 100vw;
  padding: 0;
  overflow-x: scroll;
  white-space: nowrap;
  .tab{
    display: inline-flex;
    float: none;
    &-link{
      float: left;
      padding: 10px;
      text-align: center;
    }
  }
}


.tab-panel:not(.active){
  display: none;
}
.tab-panel.active{
  display: block;
}




/* // Min width 768px */


@media (min-width: 768px) { 


/* //// Components */


 /* Modals */


  .modal{
    &-centered{
      height: 80%;
      width: 80%;
      left: 10%;
      margin: 5% auto;
    }
    &-docked-sm{
      width: 500px;
    }
    &-docked-small{
      width: 350px;
    }
  }


 /* Tabs */


  .tab-group{
    width: calc(100% + 20px);
    overflow-x: auto;
    white-space: normal;
    .tab{
      float: left;
    }
  }
}

JS

⚠️ A partir do dia 30 de janeiro de 2023, a biblioteca jQuery será removida do código de nossas lojas, portanto, a função "$" não poderá ser utilizada.

1. JavaScript precisam ser adicionados no arquivo store.js.tpl (ou onde você tem suas funções JS). O código que precisamos é o seguinte:

{# /* // Installments */ #}

{# Installments without interest #}

function get_max_installments_without_interests(number_of_installment, installment_data, max_installments_without_interests) {
    if (parseInt(number_of_installment) > parseInt(max_installments_without_interests[0])) {
        if (installment_data.without_interests) {
            return [number_of_installment, installment_data.installment_value.toFixed(2)];
        }
    }
    return max_installments_without_interests;
}

{# Installments with interest #}

function get_max_installments_with_interests(number_of_installment, installment_data, max_installments_with_interests) {
    if (parseInt(number_of_installment) > parseInt(max_installments_with_interests[0])) {
        if (installment_data.without_interests == false) {
            return [number_of_installment, installment_data.installment_value.toFixed(2)];
        }
    }
    return max_installments_with_interests;
}

{# Refresh installments inside detail popup #}

function refreshInstallmentv2(price){
    jQueryNuvem(".js-modal-installment-price" ).each(function( el ) {
        const installment = Number(jQueryNuvem(el).data('installment'));
        jQueryNuvem(el).text(LS.currency.display_short + (price/installment).toLocaleString('de-DE', {maximumFractionDigits: 2, minimumFractionDigits: 2}));
    });
}

{# Refresh price on payments popup with payment discount applied #}

function refreshPaymentDiscount(price){
    jQueryNuvem(".js-price-with-discount" ).each(function( el ) {
        const payment_discount = jQueryNuvem(el).data('paymentDiscount');
        jQueryNuvem(el).text(LS.formatToCurrency(price - ((price * payment_discount) / 100)))
    });
}

Localize a função changeVariant da função e substitua-a pelo seguinte: O código que precisamos para o carrinho é o seguinte:

{# /* // Change variant */ #}

{# Updates price, installments, labels and CTA on variant change #}

function changeVariant(variant){

    jQueryNuvem(".js-product-detail .js-shipping-calculator-response").hide();
    jQueryNuvem("#shipping-variant-id").val(variant.id);

    var parent = jQueryNuvem("body");
    if (variant.element){
        parent = jQueryNuvem(variant.element);
    }

    var sku = parent.find('#sku');
    if(sku.length) {
        sku.text(variant.sku).show();
    }

    var installment_helper = function($element, amount, price){
        $element.find('.js-installment-amount').text(amount);
        $element.find('.js-installment-price').attr("data-value", price);
        $element.find('.js-installment-price').text(LS.currency.display_short + parseFloat(price).toLocaleString('de-DE', { minimumFractionDigits: 2 }));
        if(variant.price_short && Math.abs(variant.price_number - price * amount) < 1) {
            $element.find('.js-installment-total-price').text((variant.price_short).toLocaleString('de-DE', { minimumFractionDigits: 2 }));
        } else {
            $element.find('.js-installment-total-price').text(LS.currency.display_short + (price * amount).toLocaleString('de-DE', { minimumFractionDigits: 2 }));
        }
    };

    if (variant.installments_data) {
        var variant_installments = JSON.parse(variant.installments_data);
        var max_installments_without_interests = [0,0];
        var max_installments_with_interests = [0,0];
        for (let payment_method in variant_installments) {
            let installments = variant_installments[payment_method];
            for (let number_of_installment in installments) {
                let installment_data = installments[number_of_installment];
                max_installments_without_interests = get_max_installments_without_interests(number_of_installment, installment_data, max_installments_without_interests);
                max_installments_with_interests = get_max_installments_with_interests(number_of_installment, installment_data, max_installments_with_interests);
                var installment_container_selector = '#installment_' + payment_method.replace(" ", "_") + '_' + number_of_installment;


                if(!parent.hasClass("js-quickshop-container")){
                    installment_helper(jQueryNuvem(installment_container_selector), number_of_installment, installment_data.installment_value.toFixed(2));
                }
            }
        }
        var $installments_container = jQueryNuvem(variant.element + ' .js-max-installments-container .js-max-installments');
        var $installments_modal_link = jQueryNuvem(variant.element + ' #btn-installments');
        var $payments_module = jQueryNuvem(variant.element + ' .js-product-payments-container');
        var $installmens_card_icon = jQueryNuvem(variant.element + ' .js-installments-credit-card-icon');


        {% if product.has_direct_payment_only %}
        var installments_to_use = max_installments_without_interests[0] >= 1 ? max_installments_without_interests : max_installments_with_interests;

        if(installments_to_use[0] <= 0 ) {
        {%  else %}
        var installments_to_use = max_installments_without_interests[0] > 1 ? max_installments_without_interests : max_installments_with_interests;

        if(installments_to_use[0] <= 1 ) {
        {% endif %}
            $installments_container.hide();
            $installments_modal_link.hide();
            $payments_module.hide();
            $installmens_card_icon.hide();
        } else {
            $installments_container.show();
            $installments_modal_link.show();
            $payments_module.show();
            $installmens_card_icon.show();
            installment_helper($installments_container, installments_to_use[0], installments_to_use[1]);
        }
    }

    if(!parent.hasClass("js-quickshop-container")){
        jQueryNuvem('#installments-modal .js-installments-one-payment').text(variant.price_short).attr("data-value", variant.price_number);
    }

    if (variant.price_short){
        var variant_price_clean = variant.price_short.replace('$', '').replace('R', '').replace(',', '').replace('.', '');
        var variant_price_raw = parseInt(variant_price_clean, 10);
        parent.find('.js-price-display').text(variant.price_short).show();
        parent.find('.js-price-display').attr("content", variant.price_number).data('productPrice', variant_price_raw);
    } else {
        parent.find('.js-price-display').hide();
    }

    if ((variant.compare_at_price_short) && !(parent.find(".js-price-display").css("display") == "none")) {
        parent.find('.js-compare-price-display').text(variant.compare_at_price_short).show();
    } else {
        parent.find('.js-compare-price-display').hide();
    }

    var button = parent.find('.js-addtocart');
    button.removeClass('cart').removeClass('contact').removeClass('nostock');
    var $product_shipping_calculator = parent.find("#product-shipping-container");

    {# Update CTA wording and status #}

    {% if not store.is_catalog %}
    if (!variant.available){
        button.val('{{ "Sin stock" | translate }}');
        button.addClass('nostock');
        button.attr('disabled', 'disabled');
        $product_shipping_calculator.hide();
    } else if (variant.contact) {
        button.val('{{ "Consultar precio" | translate }}');
        button.addClass('contact');
        button.removeAttr('disabled');
        $product_shipping_calculator.hide();
    } else {
        button.val('{{ "Agregar al carrito" | translate }}');
        button.addClass('cart');
        button.removeAttr('disabled');
        $product_shipping_calculator.show();
    }

    {% endif %}

    {% if template == 'product' %}
        const base_price = Number(jQueryNuvem("#price_display").attr("content"));
        refreshInstallmentv2(base_price);
        refreshPaymentDiscount(variant.price_number);


        {% if settings.last_product and product.variations %}
            if(variant.stock == 1) {
                jQueryNuvem('.js-last-product').show();
            } else {
                jQueryNuvem('.js-last-product').hide();
            }
        {% endif %}
    {% endif %}

    {# Update shipping on variant change #}

    LS.updateShippingProduct();

    zipcode_on_changevariant = jQueryNuvem("#product-shipping-container .js-shipping-input").val();
    jQueryNuvem("#product-shipping-container .js-shipping-calculator-current-zip").text(zipcode_on_changevariant);
}

Por fim, adicione a seguinte função:

{# /* // Product labels on variant change */ #}

{# Stock, Offer and discount labels update #}

jQueryNuvem(document).on("change", ".js-variation-option", function(e) {

    var $parent = jQueryNuvem(this).closest(".js-product-variants");
    var $variants_group = jQueryNuvem(this).closest(".js-product-variants-group");
    var $quickshop_parent_wrapper = jQueryNuvem(this).closest(".js-quickshop-container");

    {# If quickshop is used from modal, use quickshop-id from the item that opened it #}
    
    var quick_id = $quickshop_parent_wrapper.attr("data-quickshop-id");

    if($parent.hasClass("js-product-quickshop-variants")){

        var $quickshop_parent = jQueryNuvem(this).closest(".js-item-product");

        {# Target visible slider item if necessary #}
        
        if($quickshop_parent.hasClass("js-item-slide")){
            var $quickshop_variant_selector = '.js-swiper-slide-visible .js-quickshop-container[data-quickshop-id="'+quick_id+'"]';
        }else{
            var $quickshop_variant_selector = '.js-quickshop-container[data-quickshop-id="'+quick_id+'"]';
        }
        
        LS.changeVariant(changeVariant, $quickshop_variant_selector);


        {% if settings.product_color_variants %}
            {# Match selected color variant with selected quickshop variant #}


            if(($variants_group).hasClass("js-color-variants-container")){
                var selected_option_id = jQueryNuvem(this).find("option:selected").val();
                if($quickshop_parent.hasClass("js-item-slide")){
                    var $color_parent_to_update = jQueryNuvem('.js-swiper-slide-visible .js-quickshop-container[data-quickshop-id="'+quick_id+'"]');
                }else{
                    var $color_parent_to_update = jQueryNuvem('.js-quickshop-container[data-quickshop-id="'+quick_id+'"]');
                }
                $color_parent_to_update.find('.js-color-variant, .js-insta-variant').removeClass("selected");
                $color_parent_to_update.find('.js-color-variant[data-option="'+selected_option_id+'"], .js-insta-variant[data-option="'+selected_option_id+'"]').addClass("selected");
            }
        {% endif %} 
    } else {
        LS.changeVariant(changeVariant, '#single-product');
    }

    {# Offer and discount labels update #}

    var $this_product_container = jQueryNuvem(this).closest(".js-product-container");


    if($this_product_container.hasClass("js-quickshop-container")){
        var this_quickshop_id = $this_product_container.attr("data-quickshop-id");
        var $this_product_container = jQueryNuvem('.js-product-container[data-quickshop-id="'+this_quickshop_id+'"]');
    }
    var $this_compare_price = $this_product_container.find(".js-compare-price-display");
    var $this_price = $this_product_container.find(".js-price-display");
    var $installment_container = $this_product_container.find(".js-product-payments-container");
    var $installment_text = $this_product_container.find(".js-max-installments-container");
    var $this_add_to_cart = $this_product_container.find(".js-prod-submit-form");


    // Get the current product discount percentage value
    var current_percentage_value = $this_product_container.find(".js-offer-percentage");


    // Get the current product price and promotional price
    var compare_price_value = $this_compare_price.html();
    var price_value = $this_price.html();


    // Calculate new discount percentage based on difference between filtered old and new prices
    const percentageDifference = window.moneyDifferenceCalculator.percentageDifferenceFromString(compare_price_value, price_value);
    if(percentageDifference){
        $this_product_container.find(".js-offer-percentage").text(percentageDifference);
        $this_product_container.find(".js-offer-label").css("display" , "table");
    }


    if ($this_compare_price.css("display") == "none" || !percentageDifference) {
        $this_product_container.find(".js-offer-label").hide();
    }


    if ($this_add_to_cart.hasClass("nostock")) {
        $this_product_container.find(".js-stock-label").show();
    }
    else {
        $this_product_container.find(".js-stock-label").hide();
    }
    if ($this_price.css('display') == 'none'){
        $installment_container.hide();
        $installment_text.hide();
    }else{
        $installment_text.show();
    }
});

2. Pero también necesitamos agregar el JS que hace funcionar al componente del modal en general y para las tabs

{#/*============================================================================
      #Modals
    ==============================================================================*/ #}

{# Full screen mobile modals back events #}

if (window.innerWidth < 768) {

    {# Clean url hash function #}

    cleanURLHash = function(){
        const uri = window.location.toString();
        const clean_uri = uri.substring(0, uri.indexOf("#"));
        window.history.replaceState({}, document.title, clean_uri);
    };

    {# Go back 1 step on browser history #}

    goBackBrowser = function(){
        cleanURLHash();
        history.back();
    };

    {# Clean url hash on page load: All modals should be closed on load #}

    if(window.location.href.indexOf("modal-fullscreen") > -1) {
        cleanURLHash();
    }

    {# Open full screen modal and url hash #}

    jQueryNuvem(document).on("click", ".js-fullscreen-modal-open", function(e) {
        e.preventDefault();
        var modal_url_hash = jQueryNuvem(this).data("modalUrl");
        window.location.hash = modal_url_hash;
    });

    {# Close full screen modal: Remove url hash #}

    jQueryNuvem(document).on("click", ".js-fullscreen-modal-close", function(e) {
        e.preventDefault();
        goBackBrowser();
    });

    {# Hide panels or modals on browser backbutton #}

    window.onhashchange = function() {
        if(window.location.href.indexOf("modal-fullscreen") <= -1) {

            {# Close opened modal #}

            if(jQueryNuvem(".js-fullscreen-modal").hasClass("modal-show")){

                {# Remove body lock only if a single modal is visible on screen #}

                if(jQueryNuvem(".js-modal.modal-show").length == 1){
                    jQueryNuvem("body").removeClass("overflow-none");
                }

                var $opened_modal = jQueryNuvem(".js-fullscreen-modal.modal-show");
                var $opened_modal_overlay = $opened_modal.prev();

                $opened_modal.removeClass("modal-show");
                setTimeout(() => $opened_modal.hide(), 500);
                $opened_modal_overlay.fadeOut(500);

            }
        }
    }
}

jQueryNuvem(document).on("click", ".js-modal-open", function(e) {
    e.preventDefault(); 
    var modal_id = jQueryNuvem(this).data('toggle');
    var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="' + modal_id + '"]');
    if (jQueryNuvem(modal_id).hasClass("modal-show")) {
        let modal = jQueryNuvem(modal_id).removeClass("modal-show");
        setTimeout(() => modal.hide(), 500);
    } else {

        {# Lock body scroll if there is no modal visible on screen #}
        
        if(!jQueryNuvem(".js-modal.modal-show").length){
            jQueryNuvem("body").addClass("overflow-none");
        }
        $overlay_id.fadeIn(400);
        jQueryNuvem(modal_id).detach().appendTo("body");
        $overlay_id.detach().insertBefore(modal_id);
        jQueryNuvem(modal_id).show().addClass("modal-show");
    }             
});

jQueryNuvem(document).on("click", ".js-modal-close", function(e) {
    e.preventDefault();  
    {# Remove body lock only if a single modal is visible on screen #}

    if(jQueryNuvem(".js-modal.modal-show").length == 1){
        jQueryNuvem("body").removeClass("overflow-none");
    }
    var $modal = jQueryNuvem(this).closest(".js-modal");
    var modal_id = $modal.attr('id');
    var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="#' + modal_id + '"]');
    $modal.removeClass("modal-show");
    setTimeout(() => $modal.hide(), 500);
    $overlay_id.fadeOut(500);

    {# Close full screen modal: Remove url hash #}

    if ((window.innerWidth < 768) && (jQueryNuvem(this).hasClass(".js-fullscreen-modal-close"))) {
        goBackBrowser();
    }    
});

jQueryNuvem(document).on("click", ".js-modal-overlay", function(e) {
    e.preventDefault();
    {# Remove body lock only if a single modal is visible on screen #}

    if(jQueryNuvem(".js-modal.modal-show").length == 1){
        jQueryNuvem("body").removeClass("overflow-none");
    }
    var modal_id = jQueryNuvem(this).data('modalId');
    let modal = jQueryNuvem(modal_id).removeClass("modal-show");
    setTimeout(() => modal.hide(), 500); 
    jQueryNuvem(this).fadeOut(500);   

    if (jQueryNuvem(this).hasClass("js-fullscreen-overlay") && (window.innerWidth < 768)) {
        cleanURLHash();
    }
});

{#/*============================================================================
  #Tabs
==============================================================================*/ #}

var $tab_open = jQueryNuvem('.js-tab');

$tab_open.on("click", function (e) {
    e.preventDefault(); 
    var $tab_container = jQueryNuvem(e.currentTarget).closest(".js-tab-container");
    $tab_container.find(".js-tab, .js-tab-panel").removeClass("active");
    jQueryNuvem(e.currentTarget).addClass("active");
    var tab_to_show = jQueryNuvem(e.currentTarget).find(".js-tab-link").attr("href");
    $tab_container.find(tab_to_show).addClass("active");    
});

3. Como neste tutorial usamos a técnica de lazy load com o plugin Lazysizes, precisamos adicioná-lo. Para ver como fazer isso, você pode ler este pequeno artigo e continuar com este tutorial.

Traduções

Nesta etapa, adicionamos os textos para as traduções no arquivo config/translations.txt

es "Ver medios de pago"
pt "Ver meios de pagamento"
en "See payment options"
es_mx "Ver métodos de pago"

Pronto! Já tem a funcionalidade aplicada. Parabéns!

Novas atualizações disponíveis