Product Subscription Widget - Theme Integration
Add a subscription plan selector directly on your product pages. Customers can choose between one-time purchase and subscribe & save options before adding to cart.
Overview
The Product Subscription Widget displays selling plan options (delivery frequencies, discounts) on product pages. It automatically detects Subscribfy selling plans and shows customers their purchase options with pricing.
Features:
One-time purchase vs Subscribe & Save toggle
Automatic discount calculation and display
Multiple delivery frequency options
Two layout styles: dropdown or tiles
Variant switching support
Quick Setup
Step 1: Create the Widget Snippet
In your Shopify admin, go to Online Store → Themes → Edit code
Create a new snippet: snippets/subscribfy-product-subscription-widget.liquid
Copy the template code from the Complete Code section below.
Step 2: Add Widget to Product Page
Find your product template (usually sections/main-product.liquid or product-template.liquid)
Insert this code where you want the widget to appear (typically above the Add to Cart button):
{% if request.page_type == 'product' %}
{%- render 'subscribfy-product-subscription-widget',
product: product,
form_id: form_id,
start_onetime: false,
display_interval_selector: true,
delivery_frequency_layout: "select-list" -%}
{% endif %}
Step 3: Add Custom Script (Optional)
For stores with membership widgets, create snippets/subscribfy-custom.liquid and add this line near the end of your theme.liquid file:
{% render 'subscribfy-custom' %}
See the Complete Code section for the custom script content.
Widget Options
Configure the widget by passing parameters when rendering:
Parameter | Type | Default | Description |
| object | required | The product object. Required when not on main product page. |
| string | auto | ID of the product form. Required when widget is outside main template. |
| boolean | true | If true, "One Time" is selected by default. If false, subscription is selected. |
| boolean | true | Show/hide the delivery frequency dropdown or tiles. |
| string | "select-list" | Layout style: |
Delivery Frequency Layouts
Select List (Default)
A dropdown menu for choosing delivery frequency:
delivery_frequency_layout: "select-list"
Best for: Products with many frequency options (5+)
Tiles
Clickable boxes showing all options at once:
delivery_frequency_layout: "tiles"
Best for: Products with few frequency options (2-4)
Change Default Selection
To make "One-time Purchase" the default instead of subscription:
Go to Online Store → Edit Code
Search for
subscribfy-product-subscription-widgetFind the render statement in your product template
Change
start_onetime: falsetostart_onetime: true
Note: If the product requires a selling plan (subscription-only product), the subscription option will always be selected regardless of this setting.
Customization
CSS Classes
Style these classes to match your theme:
Class | Element |
| Main container |
| Option card (one-time or subscription) |
| Currently selected option |
| Subscription price display |
| Regular (crossed out) price |
| Discount badge (e.g., "Save 15%") |
| Frequency dropdown |
| Frequency tile (when using tiles layout) |
| Selected frequency tile |
Color Customization
The default colors in the template:
Active border:
#242424(dark gray)Discount badge:
#0fa573(green)Checkmark:
#ffbd59(gold)
Edit the <style> section in the widget template to change these.
Troubleshooting
Widget not showing?
Check that the product has Subscribfy selling plans assigned. The widget only appears for products with active selling plans.
Prices not updating on variant change?
Ensure the form_id parameter matches your theme's product form ID. Check browser console for JavaScript errors.
Duplicate widgets appearing?
The script automatically removes duplicates. If you see multiple widgets, check that you haven't included the render statement twice.
Widget conflicts with membership widget?
Add the subscribfy-custom.liquid script to hide the membership widget when product subscriptions are in the cart.
Complete Code
subscribfy-product-subscription-widget.liquid (V4.8 - 04.2025)
subscribfy-product-subscription-widget.liquid (V4.8 - 04.2025)
Latest version with tiles layout support. Copy this entire code block:
{% comment %}
V4.8 - 04.2025
product: required on non-product pages, snippets only have access to globally available variables
by default. If the product variable is defined locally, for example inside a for each product in
a collection, the snippet will not have access to the product variable unless explicitly passed.
form_id: required when plan picker is NOT redered on main product template page
start_onetime: optional to choose initial selection, defaults to true
display_interval_selector: optional, set to false to hide `exm_selling-plan-interval_wrapper`, defaults to true
delivery_frequency_layout: choose how the delivery frequency is displayed. Possible values: "select-list" (default) or "tiles"
In the sections/main-product.liquid file (note: this file may have a different name in some themes),
manually insert the code below at the location where you'd like the plan selector widget to display:
{% if request.page_type == 'product' %}
{%- render 'subscribfy-product-subscription-widget', product: product, form_id: form_id, start_onetime: false, display_interval_selector: true, delivery_frequency_layout: "tiles" -%}
{% endif %}
{% endcomment %}
{% assign current_variant = product.selected_or_first_available_variant | default: product.variants.first %}
{% assign selling_plan_selected = blank %}
{% assign has_selling_plans = false %}
{% for variant in product.variants %}
{% for allocation in variant.selling_plan_allocations %}
{% for option in allocation.selling_plan.options %}
{% if option.value contains 'Subscribfy' %}
{% assign has_selling_plans = true %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% liquid
if product.selected_selling_plan
assign selling_plan_selected = product.selected_selling_plan.id
else
for allocation in current_variant.selling_plan_allocations
unless allocation.selling_plan.options[0].value contains 'Subscribfy'
continue
endunless
assign selling_plan_selected = allocation.selling_plan.id
break
endfor
endif
%}
{% liquid
unless form_id
assign form_id = 'product-form-' | append: section.id
endunless
if start_onetime == null
assign start_onetime = true
endif
if product.requires_selling_plan
assign start_onetime = false
endif
if product.selected_selling_plan
assign start_onetime = false
endif
if display_interval_selector == null
assign display_interval_selector = true
endif
if delivery_frequency_layout == null or delivery_frequency_layout == blank
assign delivery_frequency_layout = 'select-list'
endif
%}
{% assign exm_subProd_widget_template = 2 %}
{% if exm_subProd_widget_template == 2 %}
{% if has_selling_plans and product.id != shop.metafields.exison.exison_plan_settings.product_gid %}
<div
class="exm_selling-plan-wrapper"
data-form="{{ form_id }}"
data-start_onetime="{{ start_onetime }}"
data-display_interval_selector="{{ display_interval_selector }}"
>
{% for variant in product.variants %}
<div
class="exm_selling-plan-fieldset {% if variant.id != product.selected_or_first_available_variant.id %}exm-selling-plan-hide{% endif %} sp-fieldset-vid_{{ variant.id }}"
data-id=""
>
{% unless product.requires_selling_plan %}
<label
for="selling_plan__one-time-purchase-option__vid_{{ variant.id }}"
data-variant="{{ variant.id }}"
class="selling-plan-label one-time-purchase-option {% if start_onetime == true %} exm_option--active {% endif %} "
>
<div class="sp-offer-topline">
<input
type="radio"
name="selling_plan"
class="exm_widget__option__input"
id="selling_plan__one-time-purchase-option__vid_{{ variant.id }}"
value=""
{%- if start_onetime == true and variant.id == product.selected_or_first_available_variant.id -%}
checked="checked"
{%- endif -%}
{%- if form_id -%}
form="{{ form_id }}"
{%- endif -%}
>
<span class="sp_offer_label">One Time </span>
<span class="sp_offer_reg_price">{{ variant.price | money }} </span>
</div>
<div class="sp-offer-content"></div>
</label>
{% endunless %}
<div
class="exm_selling-plans {% if start_onetime == false and variant.id == product.selected_or_first_available_variant.id %} exm_option--active {% endif %}"
data-variant="{{ variant.id }}"
>
{% for allocation in variant.selling_plan_allocations %}
{% unless allocation.selling_plan.options[0].value contains 'Subscribfy' %}
{% comment %} NOT from Subscribfy, skip to the next {% endcomment %}
{% continue %}
{% endunless %}
{% assign selling_plan_id = allocation.selling_plan.id | times: 1 %}
{% assign initial_sp_discount_text = 'Subscribe now' %}
{% assign then_sp_discount_text = blank %}
{% assign amount_discounted_percentage = blank %}
{%- if allocation.selling_plan.price_adjustments -%}
{% assign initial_adjustment = allocation.selling_plan.price_adjustments[0] %}
{% assign then_adjustment = allocation.selling_plan.price_adjustments[1] %}
{% assign initial_value = initial_adjustment.value %}
{% assign then_value = then_adjustment.value %}
{% assign initial_type = initial_adjustment.value_type %}
{% assign then_type = then_adjustment.value_type %}
{% if initial_type == 'price' %}
{% assign amount_discounted = variant.price | minus: initial_value %}
{% assign variant_price = variant.price | times: 1.0 %}
{% assign amount_discounted_percentage = amount_discounted | times: 100 | divided_by: variant_price | round %}
{% assign initial_display_value = amount_discounted | money %}
{% else %}
{% assign amount_discounted_percentage = initial_value | round %}
{% assign initial_display_value = amount_discounted_percentage | append: '%' %}
{% endif %}
{% if then_type == 'price' %}
{% assign then_display_value = then_value | money %}
{% else %}
{% assign then_display_value = then_value | round | append: '% OFF' %}
{% endif %}
{% if initial_value > 0 and initial_adjustment.order_count == blank %}
{% capture initial_sp_discount_text %}Subscribe and save <span class="sp_price_adjustments">{{ initial_display_value }}</span>{% endcapture %}
{% elsif initial_value > 0 and initial_adjustment.order_count > 0 %}
{% capture initial_sp_discount_text %}Subscribe and save {{ initial_display_value }} on first {{ initial_adjustment.order_count }} payments.{% endcapture %}
{% endif %}
{% if then_value > 0 and initial_adjustment.order_count > 0 %}
{% capture then_sp_discount_text %}Then {{ then_display_value }} {% endcapture %}
{% elsif then_value == 0 and initial_adjustment.order_count > 0 %}
{% capture then_sp_discount_text %}Then regular price{% endcapture %}
{% elsif initial_value == 0 and then_value > 0 and initial_adjustment.order_count > 0 %}
{% capture then_sp_discount_text %}Regular price on the first {{ initial_adjustment.order_count }} payments. Then {{ then_display_value }} {% endcapture %}
{% endif %}
{%- endif -%}
<label
class="
selling-plan-label subscription-purchase-option start_onetime___{{ start_onetime }} selling_plan_selected___{{ selling_plan_selected }}
{% if start_onetime == false and selling_plan_selected == selling_plan_id and variant.id == product.selected_or_first_available_variant.id %} exm_option--active {% endif %}
spo_{{ allocation.selling_plan.id }}_vid_{{ variant.id }}
"
style="{%- if selling_plan_selected == selling_plan_id and variant.id == product.selected_or_first_available_variant.id -%} {%- else -%}display:none;{%- endif -%} "
>
<div class="sp-offer-topline">
<input
type="radio"
name="selling_plan"
class="exm_widget__option__input"
id="selling_plan_{{ allocation.selling_plan.id }}_vid_{{ variant.id }}"
value="{{ selling_plan_id }}"
{%- if start_onetime == false
and selling_plan_selected == selling_plan_id
and variant.id == product.selected_or_first_available_variant.id
-%}
checked="checked"
{%- endif -%}
data-price="{{ allocation.per_delivery_price | money_with_currency }}"
{%- if form_id -%}
form="{{ form_id }}"
{%- endif -%}
>
{%- if initial_sp_discount_text != blank or then_sp_discount_text != blank -%}
<div class="sp-offer-incentive">
{{ initial_sp_discount_text }}
{{ then_sp_discount_text }}
<br>
{%- if allocation.selling_plan.price_adjustments[0].value > 0 -%}
<span class="exm_option__discount exm__widget__option__discount" data-label-discount="">
Save {{ amount_discounted_percentage }}%</span
>
{%- endif -%}
</div>
{%- endif -%}
{% if current_variant.price > allocation.per_delivery_price %}
<span class="sp_offer_reg_price">{{ current_variant.price | money }} </span>
{% endif %}
<span class="sp_offer_price">{{ allocation.per_delivery_price | money }} </span>
</div>
<div class="sp-offer-content">
<div class="sp-offer-content-body">
{% if amount_discounted_percentage > 0 %}
<span>
<span class="sp-checkmark-exm"></span> Save
{{ amount_discounted_percentage }}% on all subscriptions orders
</span>
{% endif %}
<span> <span class="sp-checkmark-exm"></span> Pause or cancel anytime </span>
<span> <span class="sp-checkmark-exm"></span> Delivery every {{ allocation.selling_plan.name }} </span>
</div>
</div>
</label>
{% endfor %}
{% if delivery_frequency_layout == "tiles" %}
<div class="exm_selling-plan-interval_wrapper__tiles">
<div class="label_delivery_every">Delivery every:</div>
<div
class="exm_selling-plan-interval_block__tiles"
>
<div
id="exm_selling_plan_vid_{{ variant.id }}"
class="exm_selling-plan-interval_tiles"
name="exm_selling_plan_vid_{{ variant.id }}"
>
{% for allocation in variant.selling_plan_allocations %}
{% unless allocation.selling_plan.options[0].value contains 'Subscribfy' %}
{% comment %} NOT from Subscribfy, skip to the next {% endcomment %}
{% continue %}
{% endunless %}
{% assign selling_plan_id = allocation.selling_plan.id | times: 1 %}
<div
class="exm_selling-plan-interval_tile
{% if selling_plan_selected == selling_plan_id and variant.id == product.selected_or_first_available_variant.id%} interval_tile_is_selected {% endif %} "
data-purchase-option="spo_{{ selling_plan_id }}_vid_{{ variant.id }}"
data-checkbox="selling_plan_{{ selling_plan_id }}_vid_{{ variant.id }}"
data-value="{{ selling_plan_id }}"
>
{{ allocation.selling_plan.name }}
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% if delivery_frequency_layout == "select-list" %}
<div
class="exm_selling-plan-interval_wrapper"
style="{% if start_onetime == true or display_interval_selector == false %} display: none;{% endif %}"
>
{% for variant in product.variants %}
<div
class="exm_selling-plan-interval_block {% if variant.id != product.selected_or_first_available_variant.id %}exm-selling-plan-hide{% endif %}"
data-variant="{{ variant.id }}"
>
<select
id="exm_selling_plan_vid_{{ variant.id }}"
class="exm_selling-plan-interval_dropdown"
name="exm_selling_plan_vid_{{ variant.id }}"
>
{% for allocation in variant.selling_plan_allocations %}
{% unless allocation.selling_plan.options[0].value contains 'Subscribfy' %}
{% comment %} NOT from Subscribfy, skip to the next {% endcomment %}
{% continue %}
{% endunless %}
{% assign selling_plan_id = allocation.selling_plan.id | times: 1 %}
<option
{% if selling_plan_selected == selling_plan_id
and variant.id == product.selected_or_first_available_variant.id
%}
selected
{% endif %}
data-plan-option="spo_{{ allocation.selling_plan.id }}_vid_{{ variant.id }}"
value="{{ allocation.selling_plan.id }}"
>
{{ allocation.selling_plan.name }}
</option>
{% endfor %}
</select>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<br>
{% endif %}
{% endif %}
<style>
.exm_selling-plan-fieldset {
display: flex;
flex-direction: column;
gap: 10px;
color: #090909;
position: relative;
}
.exm_selling-plan-fieldset input[name="selling_plan"]{
scale: 1.7;
accent-color: #000;
margin-right: 20px;
}
.exm-selling-plan-hide{
display:none !important;
}
.subscription-purchase-option:not(.exm_option--active) .sp-offer-content,
.subscription-purchase-option:not(.exm_option--active) .sp-offer-interval{
display: none;
}
.selling-plan-label.one-time-purchase-option,
.exm_selling-plans{
font-size: 15px;
display: flex;
flex-direction: column;
margin-right: 4px;
box-shadow: 0 0 5px rgba(23, 24, 24, 0.05), 0 1px 2px rgba(0, 0, 0, 0.07);
border-radius: 10px;
padding: 10px;
cursor: pointer;
background: #fff;
}
.selling-plan-label.one-time-purchase-option.exm_option--active,
.exm_selling-plans.exm_option--active{
border: 2px solid #242424;
}
.selling-plan-label .sp-offer-topline{
display: flex;
align-items: center;
width: 100%;
}
.exm_selling-plan-fieldset .selling-plan-label.subscription-purchase-option .sp_offer_label{
font-size: 1.2em;
font-weight: 700;
}
.exm_selling-plan-wrapper .sp_offer_label,
.exm_selling-plan-wrapper .sp-offer-incentive{
font-size: 1.2em;
font-weight: 700;
}
.sp_offer_reg_price{
display: block;
font-weight: 700;
font-size: 1.6em;
margin-left: auto;
}
.sp_offer_price{
font-size: 1.6em;
font-weight: 700;
margin-left: auto;
white-space: nowrap;
}
.exm_selling-plan-fieldset .selling-plan-label.subscription-purchase-option .sp_offer_reg_price{
display:none;
}
.sp-offer-incentive {
font-size: 15px;
}
.sp-offer-content-body {
display: flex;
flex-direction: column;
padding: 10px;
}
span.exm_option__discount.exm__widget__option__discount {
font-weight: 700;
color: #0fa573;
border: 1px #0fa573 solid;
padding: 0px 8px;
border-radius: 10px;
}
.sp_price_adjustments{
display: none;
}
.exm_selling-plan-interval_wrapper{
margin-top: 12px;
}
.exm_selling-plan-interval__label{
display: block;
margin-bottom: 5px;
}
.exm_selling-plan-interval_dropdown{
min-height: 44px;
border-radius: 40px;
border: 1px solid lightgrey;
padding-top: 10px;
padding-left: 18px;
padding-bottom: 10px;
background: #fff;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-position: right center;
background-image: url(https://subscribfy.nyc3.digitaloceanspaces.com/icons/ico-select.svg);
background-repeat: no-repeat;
background-position: right 10px center;
line-height: 1.2;
padding-right: 28px;
text-indent: 0.01px;
text-overflow: '';
cursor: pointer;
background-color: #ffffff;
}
.exm_selling-plan-interval_dropdown:focus,
.exm_selling-plan-interval_dropdown:focus-visible{
border-color: #000000;
outline: unset;
outline-offset: unset;
box-shadow: unset;
}
.sp-checkmark-exm {
background: transparent;
width: 8px;
transform: rotate(40deg);
height: 16px;
display: flex;
border-right: 2px solid #ffbd59;
border-bottom: 2px solid #ffbd59;
display: inline-block;
margin-right: 10px;
}
.exm_selling-plans:not(.exm_option--active) .exm_selling-plan-interval_wrapper__tiles {
display: none;
}
.exm_selling-plan-interval_wrapper__tiles {
max-width: 90%;
margin: 0 auto;
}
.exm_selling-plan-interval_tiles{
display: flex;
justify-content: space-between;
}
.label_delivery_every{
text-align: left;
}
.exm_selling-plan-interval_tile{
font-size: 13px;
display: block;
text-align: center;
margin-right: 4px;
border: 1px solid #ccc;
border-radius: 10px;
padding: 5px 20px;
cursor: pointer;
}
.interval_tile_is_selected{
border: 1px solid #0fa573;
color: #0fa573;
font-weight: bold;
}
</style>
<script>
// Ensure there's only one selling plan wrapper
const exm_sellingPlanElements = document.querySelectorAll('.exm_selling-plan-wrapper');
exm_sellingPlanElements.forEach((element, index) => {
if (index > 0) element.remove(); // Keep the first element, remove the rest
});
const sp_wrapper = document.querySelector('.exm_selling-plan-wrapper'); // Selects the first match
const exm_formAttr = sp_wrapper ? sp_wrapper.getAttribute('data-form') : null;
document.addEventListener('DOMContentLoaded', function () {
if( exm_formAttr ){
spo_observeUrlChange();
spo_observeInputValueChange();
document.body.addEventListener('click', function (e) {
// Event delegation for selling-plan-label clicks
const label = e.target.closest('.exm_selling-plan-fieldset .selling-plan-label');
if (label) {
const input = label.querySelector('input[name="selling_plan"]');
if (input) input.checked = true;
document.querySelectorAll('.selling-plan-label.exm_option--active').forEach(el => {
el.classList.remove('exm_option--active');
});
label.classList.add('exm_option--active');
document.querySelectorAll('.exm_selling-plans.exm_option--active').forEach(el => {
el.classList.remove('exm_option--active');
});
const parentFieldset = label.closest('.exm_selling-plans');
if (parentFieldset) parentFieldset.classList.add('exm_option--active');
if (sp_wrapper.getAttribute('data-display_interval_selector') === 'true') {
const intervalWrapper = document.querySelector('.exm_selling-plan-interval_wrapper');
if (intervalWrapper) {
intervalWrapper.style.display = label.classList.contains('one-time-purchase-option') ? 'none' : 'block';
}
}
}
// Handling clicks on interval tiles
const tile = e.target.closest('.exm_selling-plan-interval_tile');
if (tile) {
document.querySelector('.selling-plan-label.one-time-purchase-option.exm_option--active')?.classList.remove('exm_option--active');
document.querySelectorAll('.interval_tile_is_selected').forEach(el => {
el.classList.remove('interval_tile_is_selected');
});
tile.classList.add('interval_tile_is_selected');
const checkbox = document.getElementById(tile.getAttribute('data-checkbox'));
if (checkbox) checkbox.checked = true;
document.querySelectorAll('.subscription-purchase-option').forEach(option => option.style.display = 'none');
const purchaseOptionClass = tile.getAttribute('data-purchase-option');
if (purchaseOptionClass) {
document.querySelectorAll('.' + purchaseOptionClass).forEach(option => {
option.style.display = 'block';
option.classList.add('exm_option--active');
});
}
}
});
// Event delegation for interval dropdown changes
document.body.addEventListener('change', function (e) {
if (e.target.classList.contains('exm_selling-plan-interval_dropdown')) {
const selectedOption = e.target.selectedOptions[0];
const selectedId = selectedOption ? selectedOption.getAttribute('data-plan-option') : null;
const labels = document.querySelectorAll('.selling-plan-label.subscription-purchase-option');
labels.forEach(function (label) {
label.style.display = 'none';
label.classList.remove('exm_option--active');
});
const selectedLabel = document.querySelector('.' + selectedId);
if (selectedLabel) {
selectedLabel.style.display = 'block';
selectedLabel.click();
}
}
});
}
});
const spo_observeUrlChange = function () {
let oldHref = document.location.href;
const body = document.body;
const observer = new MutationObserver(function () {
if (oldHref !== document.location.href) {
oldHref = document.location.href;
const urlParams = new URLSearchParams(window.location.search);
const variantId = urlParams.get('variant');
exm_sp_variant_changed(variantId);
}
});
observer.observe(body, { childList: true, subtree: true });
};
const spo_observeInputValueChange = function () {
let atcForm = document.getElementById(exm_formAttr);
if (!atcForm) {
atcForm = document.querySelector('form[action*="/cart/add"]');
if (!atcForm) {
console.error('Form with action \'/cart/add\' not found.');
return;
}
}
// Determine the form and the id input/select element
var input = atcForm.querySelector('input[name="id"], select[name="id"]');
if (!input) return;
// Function to handle variant changes
function handleVariantChange(newValue) {
if (newValue !== oldInputValue) {
oldInputValue = newValue;
var variantId = newValue;
exm_sp_variant_changed(variantId);
}
}
var oldInputValue = input.value;
if (input.tagName === 'SELECT') {
// Handle change event for select lists
input.addEventListener('change', function () {
handleVariantChange(input.value);
});
} else {
// Use MutationObserver for input elements
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
handleVariantChange(input.value);
}
});
});
observer.observe(input, { attributes: true });
}
};
let lastProcessedVariantId = null;
const exm_sp_variant_changed = function (variantId) {
if (variantId === lastProcessedVariantId) {
return; // Skip if same variantId already processed
}
lastProcessedVariantId = variantId;
// Hide all selling plan fieldsets and show the relevant one
const fieldsets = document.querySelectorAll('.exm_selling-plan-wrapper .exm_selling-plan-fieldset');
fieldsets.forEach(function (fieldset) {
fieldset.classList.add('exm-selling-plan-hide');
});
const relevantFieldset = document.querySelector('.exm_selling-plan-wrapper .exm_selling-plan-fieldset.sp-fieldset-vid_' + variantId);
if (relevantFieldset) relevantFieldset.classList.remove('exm-selling-plan-hide');
// Select the selling plan option
const selectedValueElement = document.querySelector('#exm_selling_plan_vid_' + variantId);
//default is select list
var selectedValue = selectedValueElement ? selectedValueElement.value : null;
{% if delivery_frequency_layout == "tiles" %}
if (selectedValueElement) {
let selectedTile = selectedValueElement.querySelector('.exm_selling-plan-interval_tile.interval_tile_is_selected');
if (!selectedTile) {
// Fallback to first element & also add class interval_tile_is_selected
selectedTile = selectedValueElement.querySelector('.exm_selling-plan-interval_tile');
}
if (selectedTile) {
selectedTile.classList.add('interval_tile_is_selected');
var selectedValue = selectedTile.getAttribute('data-value');
}
}
{% endif %}
const startOneTime = sp_wrapper.getAttribute('data-start_onetime');
if (startOneTime === 'true') {
const oneTimeLabel = document.querySelector('.selling-plan-label[for="selling_plan__one-time-purchase-option__vid_' + variantId + '"]');
if (oneTimeLabel) oneTimeLabel.click();
const selectedLabel = document.querySelector('.selling-plan-label.spo_' + selectedValue + '_vid_' + variantId);
if (selectedLabel) selectedLabel.style.display = 'block';
const intervalWrapper = document.querySelector('.exm_selling-plan-interval_wrapper');
if (intervalWrapper) intervalWrapper.style.display = 'none';
} else {
const selectedLabel = document.querySelector('.selling-plan-label.spo_' + selectedValue + '_vid_' + variantId);
if (selectedLabel) {
selectedLabel.click();
selectedLabel.style.display = 'block';
}
}
// Plan select list
const intervalBlocks = document.querySelectorAll('.exm_selling-plan-interval_block');
intervalBlocks.forEach(function (block) {
block.classList.add('exm-selling-plan-hide');
});
const relevantIntervalBlock = document.querySelector('.exm_selling-plan-interval_block[data-variant="' + variantId + '"]');
if (relevantIntervalBlock) relevantIntervalBlock.classList.remove('exm-selling-plan-hide');
};
</script>
subscribfy-custom.liquid (Membership Integration)
subscribfy-custom.liquid (Membership Integration)
This script hides the membership widget when product subscriptions are in the cart (prevents conflicts):
{%- assign exm_product_subscription_in_cart = 0 -%}
{%- if cart.item_count > 0 -%}
{%- for item in cart.items -%}
{% comment %} skip if vip membership or Elite montly box {% endcomment %}
{% if item.product.variants[0].id == shop.metafields.exison.exison_plan_settings.product_variant_id or item.product.variants[0].id == shop.metafields.exison.exison_plan_settings2.product_variant_id %}
{% continue %}
{% endif %}
{% if item.selling_plan_allocation.selling_plan.id %}
{% assign exm_product_subscription_in_cart = 1 %}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
<script>
// Subscribfy
window.exmDev = 1;
{% if exm_product_subscription_in_cart == 1 %}
exm_setCookie('exmcart','PAYG',1);
$(document).ready(function() {
$('.exison-widget__Containe_Wrapper').addClass("hide_sp");
$("#setNoExison").trigger("click");
});
{% endif %}
document.addEventListener('subscribfy:updatedExmCartItems', function(evt) {
var vipIsSelected = $('#setExison').hasClass('exm_active');
$.ajax({
type: 'POST',
url: '/cart.js?from=subscribfy',
dataType: 'json',
success: function(cart) {
var productSubscriptionInCart = 0;
$.each(cart.items, function (index, cartItem) {
// skip vip or elite
if (cartItem.variant_id == window.exmData.plan_settings.product_variant_id || cartItem.variant_id == window.exmData.plan_settings2.product_variant_id ) {
return;
}
if( cartItem.selling_plan_allocation ){
//hide VIP widget and remove VIP product from cart
productSubscriptionInCart = 1;
$('.exison-widget__Containe_Wrapper').addClass("hide_sp");
if( vipIsSelected ){
$("#setNoExison").trigger("click");
}
}
});
if( !productSubscriptionInCart ){
$('.exison-widget__Containe_Wrapper').removeClass("hide_sp");
}
}
});
});
// ...
function exm_setCookie(name,value,hours) {
var expires = "";
if (hours) {
var date = new Date();
date.setTime(date.getTime() + (hours*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
function exm_getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function exm_eraseCookie(name) {
document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
</script>
Optional: Update Add to Cart Button Price
Add to subscribfy-custom.liquid if your theme shows price on the Add to Cart button:
Add to subscribfy-custom.liquid if your theme shows price on the Add to Cart button:
<script>
(function () {
const SELECTORS = {
VIP_WRAPPER: '.exm-pp-vip-wrapper',
VIP_RADIO: '#exm-pp-purchase-vip',
REGULAR_RADIO: '#exm-pp-purchase-regular',
ATC_PRICE: '.product__price--regular',
};
function updateATCPrice() {
const wrapper = document.querySelector(SELECTORS.VIP_WRAPPER);
const vipRadio = document.querySelector(SELECTORS.VIP_RADIO);
const atcPriceEl = document.querySelector(SELECTORS.ATC_PRICE);
if (!wrapper || !atcPriceEl) return;
const variantId = wrapper.getAttribute('data-vid');
if (!variantId) return;
const isVipJoined = wrapper.classList.contains('exm-pp-active-or-joined');
const isVipSelected = vipRadio?.checked;
const vipPrice = wrapper.getAttribute(`data-vip-price-${variantId}`)?.trim();
const regularPrice = wrapper.getAttribute(`data-regular-price-${variantId}`)?.trim();
if ((isVipJoined || isVipSelected) && vipPrice) {
atcPriceEl.textContent = vipPrice;
} else if (regularPrice) {
atcPriceEl.textContent = regularPrice;
}
}
document.addEventListener('DOMContentLoaded', () => {
updateATCPrice();
setTimeout(updateATCPrice, 2000); // Delay by x seconds
});
document.addEventListener('change', function (e) {
if (e.target.name === 'exm_pp_radio') {
updateATCPrice();
}
});
// Also observe mutation to recheck price on class change (joined state may toggle)
const wrapper = document.querySelector(SELECTORS.VIP_WRAPPER);
if (wrapper) {
const observer = new MutationObserver(updateATCPrice);
observer.observe(wrapper, { attributes: true, attributeFilter: ['class'] });
}
})();
</script>
Note: Update SELECTORS.ATC_PRICE to match your theme's price element class.
