Skip to main content

How to Add Prepaid Code to the Subscription Manager

Prepaid subscriptions may not show up in your Subscription Manager for customers to see and modify. This guide is a part of the adding prepaid subscriptions series, where we'll add the necessary code to manage prepaid subscriptions in your Subscription Manager.

Caution: The instructions laid out in this article require working knowledge of HTML, CSS, and JSON. If you're unfamiliar or uncomfortable, please reach out to support and we'll be happy to help out.


Overview

Before you open up prepaid subscriptions to everyone, customers need to be able to manage their subscriptions through the Subscription Manager. If you've recently installed Ordergroove, or have not modified the Subscription Manager templates since installation, your store might already be up to date. Follow a few of the steps below, and if you notice that the changes described are already implemented, you can skip this guide entirely. You can also create a new theme which will have prepaid code automatically installed.

If you don't want to create a new theme or do not see it in your current theme, we'll go through the changes necessary to allow customers to view and modify their prepaid subscriptions in the Subscription Manager. These modifications include:

  • The addition of a few completely new liquid and JSON files.
  • Modification of existing files.

Depending on the amount of customizations you have applied to your existing Subscription Manager templates, you may need to make some adjustments that are not covered in the guide below. 

Note: If the instructions below do not seem to match the Subscription Manager template files you are attempting to edit in the advanced editor, or if you do not feel comfortable executing these changes, do not hesitate to reach out to Ordergroove and we can help walk you through the upgrade process.

Follow along with a Video


1 - Backup your Subscription Manager

We're going to be making a number of code changes in the following steps, so let's backup the Subscription Manager configuration files just in case we want to rollback the changes.

  1. Open up your Ordergroove Admin, and go to Subscriptions > Subscription Manager.
  2. Toggle Advanced on the top left, and click Download.
  3. Your web browser will download a SMI_TEMPLATE zip file, save this file to your local machine.

Screen_Shot_2023-01-31_at_2.53.47_PM.png

Rollback Subscription Manager changes

  1. Open up your Ordergroove Admin, and go to Subscriptions > Subscription Manager.
  2. Toggle Advanced on the top left, and click Upload.
  3. Locate the smi_template file you downloaded, and upload it.
  4. Click Save on the top right.

2 - New files to add

With a backup done and ready, there are two categories of changes we're going to make; adding new files and modifying existing ones. Let's start with adding new files.

  1. Open up your Ordergroove Admin, and go to Subscriptions > Subscription Manager.
  2. Toggle Advanced on the top left.
  3. Add the files listed below to the advanced editor in the appropriate directory. Make sure the filenames and the contents of the files match what is provided below.

views/prepaid-auto-renew.liquid

Code
{% if subscription.prepaid_subscription_context %}
  {% set has_prepaid_orders_remaining = subscription.prepaid_subscription_context.prepaid_orders_remaining > 0 %}
  {% set next_order_date = 'nth_order_date(order, subscription, subscription.prepaid_subscription_context.prepaid_orders_remaining)' | js | date %}
<div class="og-autorenew-prepaid" >
<form action="{{ 'change_renewal_behavior' | action }}"
      @finally={{ 'enable_form_elements' | js }}
      @submit={{ 'disable_form_elements' | js }}
      class="og-prepaid-autorenew-form"
    >
<input type="hidden" required name="subscription" value="{{subscription.public_id}}"/>
<div class="prepaid-autorenew-checkbox-label bounce">
<input class="prepaid-autorenew-checkbox" type="checkbox" name="autorenew_on" id="autorenew-{{ order_item.public_id }}"
          ?checked="{{ subscription.prepaid_subscription_context.renewal_behavior == "autorenew" }}" @change={{ 'submit_form_onchange' | js }}>
<svg viewBox="0 0 20 20">
<polyline points="5 10.75 8.5 14.25 16 6"></polyline>
</svg>
</div>
<label for="autorenew-{{ order_item.public_id }}">
        {% if has_prepaid_orders_remaining %}
<strong>{{ 'prepaid_subscription_autorenew_checkbox' | t }}</strong>
        {% else %}
<strong>{{ 'prepaid_subscription_upcoming_renewal' | t }} {{ next_order_date | date('L') }}</strong>
        {% endif %}
</label>
</form>
    {% if subscription.prepaid_subscription_context.renewal_behavior == "autorenew" %}
      {% if has_prepaid_orders_remaining %}
<span class="og-prepaid-autorenew-date">({{'prepaid_subscription_renewal_date' | t}} {{ next_order_date | date('L') }})</span>
      {% else %}
<span class="og-prepaid-charge-message">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none">
<path fill="#515E62" fill-rule="evenodd" d="M15 9a1 1 0 0 1 1 1c0 1.654-1.346 3-3 3H3.414l1.293 1.293a.999.999 0 1 1-1.414 1.414l-3-3a.999.999 0 0 1 0-1.414l3-3a.999.999 0 1 1 1.414 1.414L3.414 11H13c.552 0 1-.45 1-1a1 1 0 0 1 1-1ZM1 7a1 1 0 0 1-1-1c0-1.654 1.346-3 3-3h9.586l-1.293-1.293A.999.999 0 1 1 12.707.293l3 3a.999.999 0 0 1 0 1.414l-3 3a.997.997 0 0 1-1.414 0 .999.999 0 0 1 0-1.414L12.586 5H3c-.552 0-1 .449-1 1a1 1 0 0 1-1 1Z" clip-rule="evenodd"/>
</svg>
          {{ 'prepaid_subscription_charge_message' | t }}
</span>
      {% endif %}
    {% endif %}
</div>
{% endif %}

views/prepaid-deliveries.liquid

Code
{# Prepaid deliveries info #}
{% if subscription.prepaid_subscription_context %}
<div class="og-prepaid-deliveries">
<div class="og-deliveries-header">
      {{ 'prepaid_deliveries_header' | t }}
</div>
<div class="og-prepaid-deliveries-container">
<div class="og-deliveries-received">{{ 'prepaid_subscription_deliveries_received' | t }} {{ (subscription.prepaid_subscription_context.prepaid_orders_per_billing - subscription.prepaid_subscription_context.prepaid_orders_remaining) }}</div>
<div class="og-deliveries-remaining">{{ 'prepaid_subscription_deliveries_remaining' | t }} {{ subscription.prepaid_subscription_context.prepaid_orders_remaining }}</div>
<div class="og-next-shipment">{{ 'prepaid_subscription_next_shipment' | t }} {{ order.place  | date('L') }}</div>
</div>{# /og-prepaid-deliveries-container #}
</div>{# /og-prepaid-deliveries #}
{% endif %}

views/prepaid-subscription-frequency.liquid

Code
{% if subscription.prepaid_subscription_context %}
<div class="og-prepaid-quantity-frequency">
<span>{{ 'item_controls_sending' | t }} </span>
<span>{{ order_item.quantity }} </span>
<span>{{ 'item_controls_every' | t }} </span>
    {% set frequency_text = 'frequency_period' | t(every = subscription.every, period = subscription.every_period) %}
<span>{{ frequency_text }} </span>
<span>{{ 'prepaid_subscription_frequency_for' | t }} </span>
<span>{{ subscription.prepaid_subscription_context.prepaid_orders_per_billing }} </span>
<span>{{ 'prepaid_subscription_frequency_deliveries' | t }} </span>
</div>
{% endif %}

 

styles/tooltips.less

Code
.og-tooltip-container {
position: relative;
display: inline-block;
}

.og-tooltip-trigger * {
pointer-events: none;
}

.og-tooltip-trigger {
all: unset;
/* preserve focus outline */
outline: revert;
}

.og-tooltip {
position: absolute;
background: white;
bottom: 2rem;
padding: 0.75rem;
border-radius: 6px;
width: 300px;
transform: translateX(-50%);
left: 50%;
filter: drop-shadow(0px 2px 1px rgba(23, 19, 47, 0.05)) drop-shadow(0px 0px 1px rgba(23, 19, 47, 0.25));
}

.og-tooltip::before {
content: '';
position: absolute;
bottom: -10px;
left: calc(50% - 10px);
border-top: 10px solid white;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
}

.og-tooltip-trigger:focus + .og-tooltip {
display: block;
}

3 - Files to Modify

Next up we need to modify a couple of existing files in the advanced editor. If you have customized your templates and the instructions bellow don't correspond to the files you see in the editor, please reach out to Ordergroove support.

  1. Open up Ordergroove, and go to Subscriptions > Subscription Manager.
  2. Toggle Advanced on the top left.
  3. Locate the files listed below in the advanced editor and add the following changes. Make sure the file changes match what is provided below.

locales/en-CA.json

Add the following entries to the top-level of the json document.

Code
  "item_badge_pay_as_you_go": "Pay-as-you-go",
  "item_badge_prepaid": "Prepaid",
  "prepaid_subscription_charge_message": "Prepaid total will be charged with upcoming shipment",
  "prepaid_subscription_deliveries_received": "Already shipped:",
  "prepaid_subscription_deliveries_remaining": "Remaining:",
  "prepaid_subscription_next_shipment": "Next shipment:",
  "prepaid_subscription_frequency_for": "for",
  "prepaid_subscription_frequency_deliveries": "deliveries",
  "prepaid_subscription_autorenew_checkbox": "Automatically renew prepaid plan",
  "prepaid_subscription_renewal_date": "Renews",
  "prepaid_subscription_upcoming_renewal": "Upcoming renewal on",
  "prepaid_deliveries_header": "Deliveries",
  "prepaid_per_delivery": "/delivery",
"change_product_prepaid_tooltip": "This product must be swapped with one of equal or lesser value",
"modal_change_product_prepaid_subtext": "Select from products that have equal or lesser value than your current product",

And add the following entries within the toasts section

Code
    "change_subscription_renewal_behavior_to_renew_success": "Success! Your subscription will be renewed",
    "change_subscription_renewal_behavior_to_not_renew_success": "Success! Your subscription will not be renewed",
    "change_subscription_renewal_behavior_error": "An error occurred while changing the behavior of your prepaid subscription. Please try again later.",
"change_product_prepaid_eligible_error": "Product needs to be prepaid eligible",
"change_product_unsent_status_error": "This subscription is not in an unsent order",
"change_product_higher_price_error": "Product price is higher than the current product price for free prepaid subscription",
"change_product_orders_needs_placement_error": "Prepaid renewal subscription has other orders that needs to be placed before changing for a more expensive product",

 

locales/en-US.json

Add the following entries to the top-level of the json document.

Code
  "item_badge_pay_as_you_go": "Pay-as-you-go",
  "item_badge_prepaid": "Prepaid",
  "prepaid_subscription_charge_message": "Prepaid total will be charged with upcoming shipment",
  "prepaid_subscription_deliveries_received": "Already shipped:",
  "prepaid_subscription_deliveries_remaining": "Remaining:",
  "prepaid_subscription_next_shipment": "Next shipment:",
  "prepaid_subscription_frequency_for": "for",
  "prepaid_subscription_frequency_deliveries": "deliveries",
  "prepaid_subscription_autorenew_checkbox": "Automatically renew prepaid plan",
  "prepaid_subscription_renewal_date": "Renews",
  "prepaid_subscription_upcoming_renewal": "Upcoming renewal on",
  "prepaid_deliveries_header": "Deliveries",
  "prepaid_per_delivery": "/delivery",
"change_product_prepaid_tooltip": "This product must be swapped with one of equal or lesser value",
"modal_change_product_prepaid_subtext": "Select from products that have equal or lesser value than your current product",

And add the following entries within the toasts section

Code
    "change_subscription_renewal_behavior_to_renew_success": "Success! Your subscription will be renewed",
    "change_subscription_renewal_behavior_to_not_renew_success": "Success! Your subscription will not be renewed",
    "change_subscription_renewal_behavior_error": "An error occurred while changing the behavior of your prepaid subscription. Please try again later.",
"change_product_prepaid_eligible_error": "Product needs to be prepaid eligible",
"change_product_unsent_status_error": "This subscription is not in an unsent order",
"change_product_higher_price_error": "Product price is higher than the current product price for free prepaid subscription",
"change_product_orders_needs_placement_error": "Prepaid renewal subscription has other orders that needs to be placed before changing for a more expensive product",

 

locales/es-ES.json

Add the following entries to the top-level of the json document.

Code
  "item_badge_pay_as_you_go": "Pago por uso",
  "item_badge_prepaid": "Prepago",
  "prepaid_subscription_charge_message": "El total prepago se cobrará con el próximo envío",
  "prepaid_subscription_deliveries_received": "Todo enviado:",
  "prepaid_subscription_deliveries_remaining": "Restantes:",
  "prepaid_subscription_next_shipment": "Próximo envío:",
  "prepaid_subscription_frequency_for": "para",
  "prepaid_subscription_frequency_deliveries": "entregas",
  "prepaid_subscription_autorenew_checkbox": "Renovar automáticamente el plan prepago",
  "prepaid_subscription_renewal_date": "Renueva",
  "prepaid_subscription_upcoming_renewal": "Próxima renovación el",
  "prepaid_deliveries_header": "Entregas",
  "prepaid_per_delivery": "/entrega",
"change_product_prepaid_tooltip": "Este producto debe ser cambiado por uno de igual o menor valor",
"modal_change_product_prepaid_subtext": "Seleccione entre productos que tienen igual o menor valor que su producto actual",

And add the following entries within the toasts section

Code
    "change_subscription_renewal_behavior_to_renew_success": "¡Éxito! Tu suscripción será renovada",
    "change_subscription_renewal_behavior_to_not_renew_success": "¡Éxito! Tu suscripción no será renovada",
    "change_subscription_renewal_behavior_error": "Ocurrió un error al cambiar el comportamiento de su suscripción prepaga. Vuelva a intentarlo más tarde.",
"change_product_prepaid_eligible_error": "El producto debe ser elegible para prepago",
"change_product_unsent_status_error": "Esta suscripción no se encuentra en un pedido no enviado",
"change_product_higher_price_error": "El precio del producto es más alto que el precio actual del producto para la suscripción gratuita de prepago",
"change_product_orders_needs_placement_error": "La renovación de la suscripción prepaga tiene otros pedidos que deben finalizados antes de cambiar por un producto más caro",

 

locales/fr-CA.json

Add the following entries to the top-level of the json document.

Code
  "item_badge_pay_as_you_go": "Paiement à l'utilisation",
  "item_badge_prepaid": "Prépayée",
  "prepaid_subscription_charge_message": "Le total prépayé sera facturé avec l'envoi à venir",
  "prepaid_subscription_deliveries_received": "Déjà reçu:",
  "prepaid_subscription_deliveries_remaining": "Restantes:",
  "prepaid_subscription_next_shipment": "Prochaine expédition:",
  "prepaid_subscription_frequency_for": "pour",
  "prepaid_subscription_frequency_deliveries": "livraisons",
  "prepaid_subscription_autorenew_checkbox": "Renouvellement automatique du forfait prépayé",
  "prepaid_subscription_renewal_date": "Renouvelle",
  "prepaid_subscription_upcoming_renewal": "Renouvellement à venir le",
  "prepaid_deliveries_header": "Livraisons",
  "prepaid_per_delivery": "/livraison",
"change_product_prepaid_tooltip": "Ce produit doit être échangé avec un produit de valeur égale ou moindre",
"modal_change_product_prepaid_subtext": "Choisissez parmi des produits qui ont une valeur égale ou inférieure à votre produit actuel",

And add the following entries within the toasts section

Code
    "change_subscription_renewal_behavior_to_renew_success": "Succès ! Votre abonnement va être renouvelé",
    "change_subscription_renewal_behavior_to_not_renew_success": "Succès ! Votre abonnement ne sera pas renouvelé",
    "change_subscription_renewal_behavior_error": "Une erreur s'est produite lors de la modification du comportement de votre abonnement prépayé. Veuillez réessayer ultérieurement.",
"change_product_prepaid_eligible_error": "Le produit doit être éligible à la prépaiement",
"change_product_unsent_status_error": "Cet abonnement n'est pas dans une commande non envoyée",
"change_product_higher_price_error": "Le prix du produit est supérieur au prix actuel du produit pour un abonnement prépayé gratuit",
"change_product_orders_needs_placement_error": "Le renouvellement prépayé de l'abonnement a d'autres commandes qui doivent être passées avant de passer à un produit plus cher",

 

styles/base.less

Add the following import after the other `@import` tags

Code
@import './tooltips.less';

Add the following anywhere within the og-smi block

Code
.og-prepaid-deliveries {
margin-top: 20px;
}

.og-deliveries-header {
font-weight: bold;
margin-bottom: 5px;
}

styles/dialogs.less

Replace the og-dialog-header block with the following:

Code
.og-dialog-header {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 0.5em;
}

Add the following anywhere in the dialogs file:

Code
.og-dialog-subtext {
font-size: 0.875rem;
}

styles/forms.less

Add the following to the very end of your forms.less

Code
.og-autorenew-prepaid {
display: flex;
align-items: center;
margin-top: 16px;
flex-wrap: wrap;

  .og-prepaid-autorenew-form {
margin-block-end: 0.5em;
display: flex;
align-items: baseline;
  }

.og-prepaid-autorenew-date {
flex-basis: 100%;
margin-left: 27px;
  }

.og-prepaid-charge-message svg {
transform: translateY(2px);
margin-right: 7px;
  }

  & > form {
    .prepaid-autorenew-checkbox-label {
display: flex;
accent-color: black;
--background: #fff;
--border: #D1D6EE;
--border-hover: #BBC1E1;
--border-active: #1E2235;
--tick: #fff;
position: relative;
font-size: 20px;
top: 3px;
margin-right: 10px;
    }

.prepaid-autorenew-checkbox-label
input, .prepaid-autorenew-checkbox-label svg {
width: 18px;
height: 18px;
min-height: 18px;
    }

.prepaid-autorenew-checkbox-label
input {
display: inline-block;
-webkit-appearance: none;
-moz-appearance: none;
position: relative;
outline: none;
background: var(--background);
border: none;
margin: 0;
padding: 0;
cursor: pointer;
border-radius: 4px;
transition: box-shadow 0.3s;
box-shadow: inset 0 0 0 var(--s, 1px) var(--b, var(--border));
    }

.prepaid-autorenew-checkbox-label input:hover {
--s: 2px;
--b: var(--border-hover);
    }

.prepaid-autorenew-checkbox-label input:checked {
--b: var(--border-active);
    }

.prepaid-autorenew-checkbox-label svg {
pointer-events: none;
fill: none;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
stroke: var(--stroke, var(--border-active));
position: absolute;
top: 0;
left: 0;
width: 18px;
height: 18px;
transform: scale(var(--scale, 1)) translateZ(0);
    }

.prepaid-autorenew-checkbox-label.path input:checked {
--s: 2px;
transition-delay: 0.4s;
    }

.prepaid-autorenew-checkbox-label.path input:checked + svg {
--a: 16.186.12;
--o: 102.22;
    }

.prepaid-autorenew-checkbox-label.path svg {
stroke-dasharray: var(--a, 86.12);
stroke-dashoffset: var(--o, 86.12);
transition: stroke-dasharray 0.6s, stroke-dashoffset 0.6s;
    }

.prepaid-autorenew-checkbox-label.bounce {
--stroke: var(--tick);
    }

.prepaid-autorenew-checkbox-label.bounce input:checked {
--s: 11px;
    }

.prepaid-autorenew-checkbox-label.bounce input:checked + svg {
-webkit-animation: bounce 0.4s linear forwards 0.2s;
animation: bounce 0.4s linear forwards 0.2s;
    }

.prepaid-autorenew-checkbox-label.bounce svg {
--scale: 0;
    }

    @-webkit-keyframes bounce {
        50% {
transform: scale(1.2);
        }

        75% {
transform: scale(0.9);
        }

        100% {
transform: scale(1);
        }
    }

    @keyframes bounce {
        50% {
transform: scale(1.2);
        }

        75% {
transform: scale(0.9);
        }

        100% {
transform: scale(1);
        }
    }
  }
}

styles/globals.less

Replace the lines @dialog-form-title-font-size: default; and @dialog-form-title-font-weight: default; with these ones:

Code
@dialog-form-title-font-size: 1rem;
@dialog-form-title-font-weight: 700;

styles/order-items.less

Add the following rules within the ".og-product" section.

Code
.og-order-item-price {
display: flex;
flex-direction: column;
align-items: flex-start;

    @media screen and (min-width: 768px) {
  align-items: flex-end;
text-align: right;
  }
}

.og-price-tag {
margin-top: 20px;
padding: 5px12px;
background-color: var(--badge-color);
border-radius: 4px;
display: inline-block;

    &[data-type="prepaid"] {
--badge-color: #A4EBFC;
    }

    &[data-type="pay-as-you-go"] {
--badge-color: #B9B0EF;
  }
}

.og-price-per-delivery {
line-height: 1.2;
}

 

Add the following within the ".og-name-price-controls-container" section:

Code
.og-product-description[data-prepaid] {
margin-bottom: 0px;
}

 

Add the following within the ".og-change-product-control" section:

Code
.og-tooltip-container {
margin-left: 0.5em;
font-size: 0.875rem;
}

.og-tooltip-trigger {
transform: translateY(3px);
}

views/order-item-price.liquid

Add the following before the existing content in the file:

Code
<div class="og-order-item-price">
    {# Only show badges if prepaid subscriptions exist #}
    {% set show_order_item_badge = (subscriptions | select('prepaid_subscription_context')).length > 0 %}
    {% if show_order_item_badge %}
      {% if is_prepaid %}
<div class="og-price-tag" data-type="prepaid">
        {{ 'item_badge_prepaid' | t }}
</div>
      {% else %}
<div class="og-price-tag" data-type="pay-as-you-go">
        {{ 'item_badge_pay_as_you_go' | t }}
</div>
      {% endif %}
    {% endif %}

    {% if is_prepaid and subscription.prepaid_subscription_context.prepaid_orders_remaining > 0 %}
      {% include 'prepaid-deliveries' %}
    {% else %}

And add the following after the existing content in the file:

Code
   {% if is_prepaid %}
<div class="og-price-per-delivery">
<span>({{ (order_item.total_price / order_item.quantity / subscription.prepaid_subscription_context.prepaid_orders_per_billing) | currency }}</span><span>{{ 'prepaid_per_delivery' | t }})</span>
</div>
    {% endif %}
  {% endif %}
</div>

 

views/orders-unsent.liquid

  1. Create a new file called order-unsent.liquid
  2. Cut or copy all of the code from views/orders-unsent.liquid within the comments: {# Unsent shipment #} and {# /og-unsent-shipment #}.
  3. Paste the code into the order-unsent.liquid file.
  4. Add the following line after the comment {# Unsent Shipment #}: {% include 'order-unsent' %}

views/order-unsent.liquid

In the previous section, we created the order-unsent.liquid file. Add the following line directly before the comment {# Order Item #}

Code
{% set is_prepaid = subscription and subscription.prepaid_subscription_context != nil %}

Replace the line <div class="og-product-description"> with:

Code
<div class="og-product-description" ?data-prepaid="{{ is_prepaid }}">

 

Look for the place in the file where this snippet below currently appears:

Code
       {% endif %}
<div class="og-mobile">{% include 'order-item-price' %}</div>
</div>

Directly after these lines, add the following snippet:

Code
            {% if is_prepaid %}
<div class="og-prepaid-frequency">
                  {% include 'prepaid-subscription-frequency' %}
                  {% include 'prepaid-auto-renew' %}
</div>
              {% else %}

And then after the line that contains </div>{# /og-item-remove-actions #} add the following:

Code
{% endif %}

 

views/change-product.liquid

Replace this line {% set alternative_ids = get(sku_swap, 'products.' + product.external_product_id, []) %} with this code:

Code
{% set alternative_ids = get(sku_swap, 'subscriptions.' + subscription.public_id, []) %}
{% set is_prepaid_fulfillment = is_prepaid and subscription.prepaid_subscription_context.prepaid_orders_remaining > 0 %}

Inside the og-change-product-control div and after the first <a><a/> link element add this tooltip code:

Code
    {% if is_prepaid_fulfillment %}
<div class='og-tooltip-container'>
<button
class='og-tooltip-trigger'
aria-label='information'

aria-describedby='og-sku-swap-tooltip-{{subscription.public_id}}'
@mouseenter='{{ 'show_closest_tooltip' | js }}'
@mouseout='{{ 'hide_closest_tooltip' | js }}'
>
<svg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path d="M8.99984 0.666664C4.39779 0.666664 0.666504 4.39929 0.666504 9C0.666504 13.6034 4.39779 17.3333 8.99984 17.3333C13.6019 17.3333 17.3332 13.6034 17.3332 9C17.3332 4.39929 13.6019 0.666664 8.99984 0.666664ZM8.99984 4.3629C9.77927 4.3629 10.4111 4.99476 10.4111 5.77419C10.4111 6.55363 9.77927 7.18548 8.99984 7.18548C8.2204 7.18548 7.58855 6.55363 7.58855 5.77419C7.58855 4.99476 8.2204 4.3629 8.99984 4.3629ZM10.8816 12.8978C10.8816 13.1205 10.701 13.3011 10.4783 13.3011H7.52134C7.29866 13.3011 7.11812 13.1205 7.11812 12.8978V12.0914C7.11812 11.8687 7.29866 11.6882 7.52134 11.6882H7.92457V9.53763H7.52134C7.29866 9.53763 7.11812 9.35709 7.11812 9.13441V8.32795C7.11812 8.10527 7.29866 7.92473 7.52134 7.92473H9.67188C9.89456 7.92473 10.0751 8.10527 10.0751 8.32795V11.6882H10.4783C10.701 11.6882 10.8816 11.8687 10.8816 12.0914V12.8978Z" fill="#474747"/>
</svg>
</button>

<div class='og-tooltip' id='og-sku-swap-tooltip-{{subscription.public_id}}' hidden>{{ 'change_product_prepaid_tooltip' | t }}</div>
</div>
{% endif %}

Now, inside the div with class og-dialog-header add this code before the button:

Code
         <div>
<h5 class="og-dialog-title">{{ 'modal_change_product_header' | t }}</h5>
{% if is_prepaid_fulfillment %}
<p class="og-dialog-subtext">{{ 'modal_change_product_prepaid_subtext' | t }}</p>
{% endif %}
</div>
<

scripts/script.js

Add these code on the end of the file:

Code
function show_closest_tooltip(ev) {
const tooltip = ev.currentTarget.parentNode.querySelector('.og-tooltip');
tooltip.hidden = false;
}

function hide_closest_tooltip(ev) {
const tooltip = ev.currentTarget.parentNode.querySelector('.og-tooltip');
tooltip.hidden = true;
}