Create subscription bundling with Shopify Checkout Integration

This guide explains how to create a customer bundling solution.

🚧

Note

This guide outlines how to create a custom bundling solution outside of Recharge's Bundles feature. See Bundles in the Recharge Help Center for additional information about Recharge's offering.

You can let customers bundle subscriptions with Recharge, allowing them to "build-a-box," mixing and matching different products. Adding this feature can help increase average order value and annual revenue, move inventory, and provide a better customer experience.

This guide will walk you through the steps for creating bundling functionality on a store using custom HTML, JavaScript, and liquid. The final portion of the guide will walk you through making the necessary changes within the Recharge Theme Engine.

Prerequisites

Key concepts

  • This example features a customizable 12-pack of soda, where customers can choose which twelve individual cans of soda they’d like to include in their pack.
  • Each distinct bundle option is set up as a separate SKU in Shopify. Each bundle option is a different variant (flavor) of an overarching product.
  • When a customer adds the selected bundle to the cart, 12 line items are added to the cart: one for each item in the bundle.
  • In order to tie the bundle items together, you will create a unique identifier (a bundle ID) and set it as a custom line item property for each item. You will reference this bundle ID when grouping the bundle items together within the Shopify cart and Recharge customer portal.

🚧

Note

This guide and the code samples below should serve as an example demonstrating one way to achieve bundling. The following snippets are meant to serve as a reference and are not intended for use in a production environment.

Setup parent subscription product in Shopify

First, you'll need to create a base product in Shopify that will be the "parent" product for the build-a-box. This product will have all six variants, one for each flavor.

  1. Create a product called "Build-a-Box" in Shopify and add a flavor variant with six options: "Apple," "Cherry," "Cola," "Peach," "Raspberry," and "Vanilla". The total box price is $24, therefore set each individual
    variant price to $2. When a customer adds twelve flavors to their bundle, the total will equal $24.

Create the product in Recharge

After you've created the bundle product and variants in Shopify, you will need to add the product to Recharge.

  1. Within Recharge, navigate to Products > Add Products and select the "Build-a-box" product.
  2. Select the subscription type you’d like to offer, then add a discount, order frequency,
    and any other subscription options and save.

📘

Adding an active product to Recharge

You can only add an active product to Recharge. If you want to add a hidden product, make the product active in Shopify, add it to Recharge, then set it back to inactive.

Build a product display page in Shopify

Now that the product is configured in Shopify and Recharge, next you will edit the
Shopify theme files to create a custom product page template.

Creating a new product display template

This template will display each
bundle option and allow customers to choose which 12 flavors they’d like in their bundle.

  1. Create a new section called product-bundle.liquid.
  2. Copy the contents of the main-product.liquid file and paste them into the new
    product-bundle.liquid section. You'll use this as a base for the new product display section.
  3. Create a new JSON product template called product.bundle.json.
  4. Copy the contents of the product.json template and paste them into the new
    product.bundle.json template. You'll use this as a base for the new JSON required to build the new template.
  5. Update the section to point to this newly created product bundle file by
    updating the sections.main.type value to ‘product-bundle’.

📘

Note

Set the show_dynamic_checkout buy button setting to false. You don’t want users to be able to use
other dynamic buy buttons outside of the add-to-cart button on the product page.

  1. Finally, update the product template in Shopify to use the bundle file.

Update liquid and HTML

Now that you've created the framework for a new product display page, it's time to add the new liquid and HTML to power the bundle product page. If you aren’t familiar with editing Shopify themes, check out Shopify’s guide for more information.

Create minimum and maximum count variables

These two variables represent the minimum and maximum counts the JavaScript functions will reference to manipulate the DOM when users add/remove items to their bundle, then add the bundle to cart.

{% assign bundle_min = 0 %}
{% assign bundle_max = 12 %}

Grid layout

  • Structure the bundle options in a grid format with a plus and minus button for each option to allow customers to and or remove items from the bundle.
  • Create the following key div classes on the page:
    • bundle - This class acts as a container for the variant grid. It will also act as the top-level item for most of the DOM traversal contained in the script.
    • bundle-grid - This class holds data attributes for the minimum and maximum variables. You will use these values in the script functions to maintain constraint integrity when the user is clicking the plus and
      minus buttons, as well as to determine when the user should be allowed to add the bundle to the cart.
    • bundle-item - This class represents each individual variant that is part of
      the grid matrix. It holds data attributes for variant_id and quantity. These two data attributes will be used per variant when you add the bundle to the cart to determine the quantity of each variant.
    • bundle-item-selector - This class represents the controls the user can interact with for each variant (i.e. the plus and minus buttons). There is also a quantity item within this div that will visually show the user how
      much of this variant is currently selected.

Product information container

  • Use the standard product information container in the Dawn theme with some adjustments.
    • Price - Display the full bundle price rather than just one variant price by removing the content in the price div and adding the bundle_price class. The updatePrice() function in the next section will target this element and dynamically update the price as bundle items are selected.
    • Variant selector- You need to remove the variant selector section, because users will select variants in the custom bundle grid.
    • Add to cart form - Use the standard Dawn add-to-cart form and add id=‘bundle-add-to-cart‘ to the button element so the script can target this element.

Full product page code

{% assign bundle_min = 0 %}
{% assign bundle_max = 12 %}
<link rel="stylesheet" href="{{ 'component-cart-notification.css' | asset_url }}" media="print" onload="this.media='all'">
    <link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'">
        <section class="page-width">
            <div class="product grid bundle grid--1-col {% if product.media.size > 0 %}grid--2-col-tablet{% else %}product--no-media{% endif %}">
                <div class="grid__item product__media-wrapper">
                    <slider-component class="slider-mobile-gutter">
                        <div class="grid bundle-grid" data-bundle-min="{{ bundle_min }}" data-bundle-max="{{ bundle_max }}" data-subscription-price="{{ bundle_subscription_price }}" data-onetime-price="{{ bundle_onetime_price }}">
                            {% for variant in product.variants %}
                                <div class="grid__item bundle-product-grid-item">
                                    <div class="grid bundle-item" data-variant-id="{{ variant.id }}" data-qty="0">
                                        <img src="{{ variant.image.src | img_url: '200x200' }}" alt="{{ variant.title }}" class="bundle_img">
                                            <div class="h3">{{ variant.title }}</div>
                                            {% if variant.available == false %}
                                                <span class="h6 block">Out of stock</span>
                                            {% else %}
                                                <div class="bundle-item-selector">
                                                    <span class="minus">
                                                        <button class="btn" disabled>-</button>
                                                    </span>
                                                    <span class="bundle_quantity">0</span>
                                                    <span class="plus">
                                                        <button class="btn">+</button>
                                                    </span>
                                                </div>
                                            {% endif %}
                                        </div>
                                    </div>
                                {% endfor %}
                            </div>
                        </slider-component>
                    </div>
                    <div class="product__info-wrapper grid__item">
                        <div id="ProductInfo-{{ section.id }}" class="product__info-container{% if
                                section.settings.enable_sticky_info %} product__info-container--sticky{% endif %}">
                            {%- assign product_form_id = 'product-form-' | append: section.id -%}
                            {%- for block in section.blocks -%}
                                {%- case block.type -%}
                                    {%- when '@app' -%}
                                        {% render block %}
                                    {%- when 'text' -%}
                                        <p class="product__text{% if block.settings.text_style == 'uppercase' %}
                                                caption-with-letter-spacing{% elsif block.settings.text_style == 'subtitle' %} subtitle{% endif %}">
                                            {{ - block.settings.text - }}
                                        </p>
                                    {%- when 'title' -%}
                                        <h1 class="product__title">
                                            {{ product.title | escape }}
                                        </h1>
                                    {%- when 'price' -%}
                                        <div class="no-js-hidden bundle-price price--large" id="price-{{ section.id }}"></div>
                                        <div class="bundle-qty">
                                            Items:
                                            <span class="current-qty">0</span>
                                            of
                                            {{ bundle_max }}
                                        </div>
                                        {%- form 'product', product, data-productid: product.id, id: 'product-form-installment',
class: 'installment caption-large' -%}
                                            <input type="hidden" name="id" data-productid="{{ product.id }}" value="{{ product.selected_or_first_available_variant.id }}">
                                                {{ form | payment_terms }}{%- endform -%}
                                            {%- when 'description' -%}
                                                {%- if product.description != blank -%}
                                                    <div class="product__description rte">
                                                        {{ product.description }}
                                                    </div>
                                                {%- endif -%}
                                                …
                                            {%- when 'buy_buttons' -%}
                                                <product-form class="product-form">
                                                    {%- form 'product', product, data-productid: product.id, id: product_form_id, class:
'form', novalidate: 'novalidate', data-type: 'add-to-cart-form' -%}
                                                        <input type="hidden" name="id" data-productid="{{ product.id }}" value="{{ product.selected_or_first_available_variant.id }}">
                                                            <div class="product-form__buttons bundle-add-to-cart">
                                                                <button id="bundle-add-to-cart" type="submit" name="add" class="product-form__submit button button--full-width {% if
                                                                    block.settings.show_dynamic_checkout and product.selling_plan_groups == empty %}button--secondary{% else %}button--primary{% endif %}" disabled>
                                                                    {%- if product.selected_or_first_available_variant.available -%}
                                                                        {{ 'products.product.add_to_cart' | t }}
                                                                    {%- else -%}
                                                                        {{ 'products.product.sold_out' | t }}
                                                                    {%- endif -%}
                                                                </button>
                                                            </div>
                                                        {%- endform -%}
                                                    </product-form</div> {%- endcase -%} {%- endfor -%}
                                                </div>
                                            </div>

Add script for bundling page to Shopify theme

The next step is creating the JavaScript file that will contain the logic for the bundling functionality. Create a new file in the assets directory called bundle.js. View the commented code below for an explanation of how each function is used.

Full product page JS

Bundle = {
  Defaults: {
    // Convenience functions to reference various important divs within the build.
    // If you structure your HTML differently than our example build, you can alter these
    // functions to reference the correct divs.
    getBundleDiv: function () {
      return $(".bundle");
    },
    getBundleGridDiv: function () {
      return Bundle.Defaults.getBundleDiv().find(".bundle-grid");
    },
    getBundleDetailsDiv: function () {
      return Bundle.Defaults.getBundleDiv().find(".bundle-details");
    },
    getAddToCartDiv: function () {
      return $("#bundle-add-to-cart");
    },
    getWidget: function () {
      return ReChargeWidget.getWidgets()[0];
    },
  },
  Utils: {
    // Utilty functions used throughout the rest of the build
    generateGuid: function () {
      // Generates a unique guid to be used for the bundle_id
      return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
        /[xy]/g,
        function (c) {
          var r = (Math.random() * 16) | 0,
            v = c == "x" ? r : (r & 0x3) | 0x8;
          return v.toString(16);
        }
      );
    },
    calculateBundleQuantity: function ($bundle) {
      // Calculates the total number of items selected on the page.
      // Will loop through all grid items and sum up the total bundle
      // quantity based on the data-qty attribute of each variant.
      // 1.) $bundle = top level bundle div of the grid matrix
      var bundle_qty = 0;
      $bundle.find(".bundle-item").each(function () {
        bundle_qty += Number($(this).attr("data-qty"));
      });
      return bundle_qty;
    },
    isSubscription: function (widget) {
      // Determines whether the user has selected the One-time purchase
      // or Subscribe and Save option on the ReCharge widget
      return widget.subscriptionActive == true;
    },
    updateBundleQuantity: function ($item, increment) {
      // Main function to handle quantity manipulation. Pass in the grid
      // item along with the quantity to increment or decrement. Then it will
      // determine the current and new item/bundle quantities. Then it checks
      // constraints, and if valid, update all necessary data attributes,
      // interface output, and enable/disable any buttons.
      // 1.) $item = the clicked plus or minus .bundle-item parent div
      // 2.) increment = the amount to increase or decrease the quantity
      // Get bundle parent reference and min/max numbers
      var $bundle = $item.closest(".bundle-grid");
      var bundle_min = Number($bundle.attr("data-bundle-min"));
      var bundle_max = Number($bundle.attr("data-bundle-max"));
      // Calculate current item and bundle quantity
      var bundle_qty = Bundle.Utils.calculateBundleQuantity($bundle);
      var item_qty = Number($item.attr("data-qty"));
      // Calculate the new bundle and item quantities with the increment added
      var new_bundle_qty = bundle_qty + increment;
      var new_item_qty = item_qty + increment;
      // Check constraints to make sure we can perform the action
      if (
        Bundle.Constraints.isItemGreaterThanZero(new_item_qty) &&
        Bundle.Constraints.isBundleGreaterThanMin(new_bundle_qty, bundle_min) &&
        Bundle.Constraints.isBundleLessThanMax(new_bundle_qty, bundle_max)
      ) {
        //Update quantities
        $item.attr("data-qty", new_item_qty);
        $item.find(".bundle_quantity").html(new_item_qty);
        $(".current-qty").html(new_bundle_qty);
        //Toggle the Add to Cart Button if max items is reached
        var is_disabled = Bundle.Constraints.isBundleEqualMax(
          new_bundle_qty,
          bundle_max
        )
          ? false
          : true;
        Bundle.Defaults.getAddToCartDiv().prop("disabled", is_disabled);
        //Update plus/minus button's disabled property
        $bundle.find(".bundle-item").each(function () {
          var item_qty = $(this).attr("data-qty");
          var is_disabled = Bundle.Constraints.isBundleEqualParam(
            new_bundle_qty,
            bundle_max
          )
            ? true
            : false;
          $(this).find(".plus").find(".btn").prop("disabled", is_disabled);
          var is_disabled =
            Bundle.Constraints.isBundleEqualParam(new_bundle_qty, bundle_min) ||
            Bundle.Constraints.isBundleEqualParam(item_qty, 0)
              ? true
              : false;
          $(this).find(".minus").find(".btn").prop("disabled", is_disabled);
        });
      }
    },
    updatePrice: function () {
      var widget = Bundle.Defaults.getWidget();
      var is_subscription = Bundle.Utils.isSubscription(widget);
      var $bundle = Bundle.Defaults.getBundleGridDiv();
      var bundle_subscription_price = $bundle.attr("data-subscription-price");
      var bundle_onetime_price = $bundle.attr("data-onetime-price");
      //If subscription option is clicked, display discounted subscription price
      //Otherwise, display full bundle price
      if (is_subscription) {
        $(".bundle-price").html(bundle_subscription_price);
      } else {
        $(".bundle-price").html(bundle_onetime_price);
      }
    },
  },
  Constraints: {
    // Requirement functions to determine whether the user is
    // performing an action that is forbidden
    isItemGreaterThanZero: function (qty) {
      // Check that the item quantity doesn't fall below zero
      // 1.) qty = item quantity of the variant
      if (qty < 0) {
        console.log("Item quantity cannot be less than 0.");
        return false;
      }
      return true;
    },
    isBundleGreaterThanMin: function (qty, min) {
      // Check that the bundle quantity doesn't fall below the minimum quantity
      // 1.) qty = total bundle quantity of the page
      // 2.) min = minimum value the bundle quantity can't fall below
      if (qty < min) {
        console.log("Quantity cannot be less than the minimum.");
        return false;
      }
      return true;
    },
    isBundleLessThanMax: function (qty, max) {
      // Check that the bundle quantity doesn't go above the maximum quantity
      // 1.) qty = total bundle quantity of the page
      // 2.) max = maximum value the bundle quantity can't rise above
      if (qty > max) {
        console.log("Quantity cannot be greater than maximum.");
        return false;
      }
      return true;
    },
    isBundleEqualMax: function (qty, max) {
      // Check that the bundle quantity is equal to the maximum quantity
      // 1.) qty = total bundle quantity of the page
      // 2.) max = maximum value the bundle quantity can't rise above
      return qty == max ? true : false;
    },
    isBundleEqualParam: function (qty, param) {
      // Check that the bundle quantity is equal to param
      // 1.) qty = total bundle quantity of the page
      // 2.) param = value to compare qty against
      return qty == param ? true : false;
    },
  },
  Cart: {
    // Functions used when adding the bundle to the cart and removing a bundle from the cart
    processAddToCart: function () {
      // Top level function tied to the Add to Cart button.
      // Function will check constraints to make sure we can
      // add the bundle to the cart, then it will generate
      // the cart array and make the AJAX call to Shopify's API.
      // Once added to cart, we redirect to the /cart page.
      // Get bundle parent, bundle max value, and total bundle quantity values
      var $bundle = Bundle.Defaults.getBundleGridDiv();
      var qty = Bundle.Utils.calculateBundleQuantity($bundle);
      var bundle_max = Number($bundle.attr("data-bundle-max"));
      // Ensure we can process the cart, and if not, break out of function
      if (!Bundle.Constraints.isBundleEqualMax(qty, bundle_max)) {
        console.log(
          "Cannot process Add to Cart. Qty does not equal max total."
        );
        return;
      }
      //Generate the product array to pass to AJAX call
      var widget = Bundle.Defaults.getWidget();
      var items = Bundle.Cart.generateCart($bundle, widget);
      // Ensure there are items to add to cart, and if so, make
      // API call and redirect to /cart page
      if (items.length > 0) {
        Bundle.Cart.processToCart(items);
      } else {
        console.log("No items available to add to cart.");
      }
    },
    generateCart: function ($bundle, widget) {
      // Check that the bundle quantity is equal to param
      // 1.) $bundle = top level bundle div of the grid matrix
      // 2.) widget = the ReCharge widget object tied to the page
      var cart = [];
      var is_subscription = Bundle.Utils.isSubscription(widget);
      // bundle_id that we will pass to each product object as a
      // line item property to associate them to this bundle
      var bundle_id = Bundle.Utils.generateGuid();
      // Determine whether the user has the One-time purchase or
      // Subscribe & Save purchase type option selected. If Subscribe
      // & Save, generate the shipping interval unit_type and frequency
      // that are passed through on ReCharge orders.
      if (is_subscription) {
        var selling_plan = widget.state.sellingPlanId;
      }
      // Loop through each product grid item
      $bundle.find(".bundle-item").each(function () {
        // If item's quantity is greater than 0, add
        // to the cart array
        var qty = $(this).attr("data-qty");
        if (Number(qty) > 0) {
          var variant_id = $(this).attr("data-variant-id");
          // Pass the variant_id, quantity, and bundle_id
          // for each grid item we process
          var item = {
            id: variant_id,
            quantity: qty,
            properties: {
              bundle_id: bundle_id,
            },
          };
          // If this is a Subscribe & Save bundle, pass in the
          // ReCharge properties too
          if (is_subscription) {
            item["selling_plan"] = selling_plan;
          }
          cart.push(item);
        }
      });
      return cart;
    },
    processToCart: function (items) {
      // Makes API call to add bundle items to cart. If the
      // call is successful, redirect to /cart page. If error,
      // display the error to the console.
      // 1.) items = the product array to be passed to the Shopify
      // API. Bundle.Cart.generateCart() function builds this array.
      $.ajax({
        type: "POST",
        url: "/cart/add.js",
        data: {
          items: items,
        },
        dataType: "json",
        success: function () {
          window.location.href = "/cart";
        },
        error: function (jqXHR, text_status, error_thrown) {
          console.log(text_status, error_thrown);
        },
      });
    },
    removeBundleItems: function (bundle_id) {
      // Removes all bundle items from cart when user clicks remove on cart page
      // Get current cart contents
      jQuery.getJSON("/cart.js", function (cart) {
        // Create new updates object to send to cart update AJAX call
        // Loop through each cart item
        // If the item's bundle id matches the id passed in,
        // add the cart item key along with quantity of 0 to the data object
        let bundle_items = [];
        let qty = 0;
        let data = { updates: {} };
        cart.items.forEach((item, i) => {
          if (
            item.properties.bundle_id &&
            item.properties.bundle_id == bundle_id
          ) {
            data.updates[item.key] = 0;
          }
        });
        // API call to update the cart contents and set the bundle item quantities to 0
        $.ajax({
          type: "POST",
          url: "/cart/update.js",
          data: data,
          dataType: "json",
          success: function () {
            window.location.href = "/cart";
          },
        });
      });
    },
  },
  init: function () {
    // Initialization function to register all click handlers
    // Registers each grid item's minus button
    $("body").on("click", ".bundle .minus", function () {
      var item = $(this).closest(".bundle-item");
      Bundle.Utils.updateBundleQuantity(item, -1);
    });
    // Registers each grid item's plus button
    $("body").on("click", ".bundle .plus", function () {
      var item = $(this).closest(".bundle-item");
      Bundle.Utils.updateBundleQuantity(item, 1);
    });
    // Registers the page's Add to Cart button
    $("body").on("click", "#bundle-add-to-cart", function () {
      event.preventDefault();
      Bundle.Cart.processAddToCart();
    });
    // Registers function to handle price change when user changes subscription vs one-time
    $("body").on("change", ".rc-widget input", function () {
      Bundle.Utils.updatePrice();
    });
    // Registers each cart bundle item's remove button
    $("body").on("click", ".cart__remove-bundle", function () {
      var bundle_id = $(this)
        .closest(".cart__remove-bundle")
        .attr("data-bundle-id");
      Bundle.Cart.removeBundleItems(bundle_id);
    });
  },
};
// Call the init() function to register click handlers
Bundle.init();

Updating the cart page

Cart

With the standard cart page, the customer would see all twelve variants listed out separately
and be able to adjust the quantity of each. In this step, bundle the items together on the cart page and add functionality to remove the bundle as a whole and restrict the ability to remove individual variants.

This example uses the existing structure in the main-cart-items.liquid file, however, you’ll render an adjusted cart item snippet if the item is a part of a bundle.

Key concepts

  • Bundles_displayed: In order to keep track of which bundles are already displayed, begin by creating a blank liquid variable, bundles_displayed. As you render each bundle on the cart page, you will add bundle IDs to this variable.
{% assign bundles_displayed = '' | split:',' %}
  • Add logic to cart loop - Within the cart item table, the existing code loops through each
    item in the cart to display a new row. Add the following conditional statements to this existing loop.
    • If the item is a part of a bundle (i.e. if it has a bundle_id line item property),
      check to see if the bundle has already been displayed (i.e. check if the ID is in the bundles_displayed list).
    • If it hasn’t been displayed, append it to the bundles_displayed list and
      render a new liquid file, cart-item-bundle.liquid.
    • If the item is not a part of a bundle, display the standard cart item row
      found in the main-cart-items.liquid file.
{%- for item in cart.items -%}
    {%- if item.properties['bundle_id'] -%}
        {%- unless bundles_displayed contains item.properties['bundle_id'] -%}
            {%- assign bundles_displayed = item.properties['bundle_id'] | concat: bundles_displayed -%}
            {%- render 'cart-item-bundle', item: item, cart: cart, current_bundle_id:
item.properties['bundle_id'] -%}
        {%- endunless -%}
    {%- else -%}

Create a new bundle cart item snippet

Create a new snippet called cart-item-bundle.liquid, and copy/paste the table row
HTML from the main cart items file, making the following adjustments.

  • Update the image - Because you are displaying the main bundle image rather than the
    individual variant images, update the code to display the main Shopify product
    image.
<img class="cart-item__image" src="{{ item.image | img_url: '150x' }}" alt="{{ item.image.alt | escape }}" loading="lazy" width="75" height="{{ 75 | divided_by: item.image.aspect_ratio | ceil }}">
  • Display all variant titles and quantities - To show the contents of the bundle, replace
    the existing variant loop, {%- for option in item.options_with_values -%}, with the
    following code which loops through the cart items and renders the variant titles and
    quantities.
{%- for item in cart.items -%}
    {%- if item.properties['bundle_id'] == current_bundle_id -%}
        <div class="product-option">
            <dt>{{ item.variant.title }}:
            </dt>
            <dd>{{ item.quantity }}</dd>
        </div>
    {%- endif -%}
{%- endfor -%}
  • Hide the bundle_id line item property Hide the bundle_id, as it isn’t
    relevant to the customer.
{%- if property.last != blank and property_first_char != '_' and property.first != 'bundle_id' -%}
  • Update the price - Display the total bundle price, rather than just the price of
    one variant. Update the price sections in this snippet. Define a new bundle_price variable set to 0 and loop through each cart item. If the item is in the bundle, add the price to the bundle_price variable. Rather than creating a new loop, add the liquid math filter to the loop in the
    "Display all variant titles and quantities" section above. Update each price section with this new bundle_price variable.
{% assign bundle_price = 0 %}
...
{%- for item in cart.items -%}
    {%- if item.properties['bundle_id'] == current_bundle_id -%}
        {%- assign bundle_price = item.price | times: item.quantity | plus: bundle_price -%}
        <div class="product-option">
            <dt>{{ item.variant.title }}:
            </dt>
            <dd>{{ item.quantity }}</dd>
        </div>
    {%- endif -%}
{%- endfor -%}
...
<span class="price price--end">
    {{ bundle_price | money }}
</span>
  • Remove ability to update quantity - You don’t want customers to update the
    quantity for bundles, because the bundle logic is only set on the product page. Replace the quantity input element with the following.
    <input class="quantity__input" value="1" readonly>
  • Update the cart remove button - When a customer clicks the remove button on a bundle
    item, you must remove all of the bundle's contents. Add a new data attribute on
    the element that contains the current bundle ID, so the function can identify which items to remove.
<cart-remove-button class="cart__remove-bundle" data-bundle-id="{{ current_bundle_id }}">
    <a href="{{ item.url_to_remove }}" class="button button--tertiary" aria-label="{{ 'sections.cart.remove_title' | t: title: item.title }}">
        {% render 'icon-remove' %}
    </a>
</cart-remove-button>

Add a script to remove all bundle items

You need a function and event listener to handle the removal of all bundle items when a customer clicks the "remove" button on the cart page. Add the following items code to the existing bundle.js file:

  • Bundle.Cart.removeBundleItems() - Add a new function to the cart object that
    accepts the bundle_id of the bundle that should be removed from cart. Get the current cart contents, add the items that make up the current bundle to a data object, and make an AJAX call to remove these items from the cart. Note that you should use the Shopify item key in the updates object rather than the variant ID to ensure you are removing the correct variant associated with the bundle ID.
removeBundleItems : function (bundle_id) {
    // Removes all bundle items from cart when user clicks remove on cart page
    // Get current cart contents
    jQuery.getJSON('/cart.js', function (cart) {
        // Create the data object for the cart update AJAX call
        // Loop through each cart item
        // If the item's bundle id matches the id passed in, add the
        // cart item key along with a quantity of 0 to the data object
        let bundle_items = [];
        let qty = 0;
        let data = {
            updates: {}
        };
        cart
            .items
            .forEach((item, i) => {
                if (item.properties.bundle_id && item.properties.bundle_id == bundle_id) {
                    data.updates[item.key] = 0;
                }
            })
            // API call to update the cart contents and set the
            // bundle item quantities to 0
            $
            .ajax({
                type: 'POST',
                url: '/cart/update.js',
                data: data,
                dataType: 'json',
                success: function () {
                    window.location.href = '/cart';
                }
            });
    });
}
  • Event listener on cart remove button - Add a new event listener to trigger the Bundle.Cart.removeBundleItems(bundle_id) function when a customer clicks the cart remove button on a bundle item.
// Registers each cart bundle item's remove button
$('body').on('click', '.cart__remove-bundle', function () {
    var bundle_id = $(this).closest('.cart__remove-bundle').attr('data-bundle-id');
    Bundle.Cart.removeBundleItems(bundle_id);
});
  • Update event listener in cart.js - Because you've added an event listener for the bundle remove button, update the existing cart item removal event listener in the cart.js file. Add an if statement to only perform the action if it’s a non-bundle item.
class CartRemoveButton extends HTMLElement {
  constructor() {
    super();
    this.addEventListener("click", (event) => {
      event.preventDefault();
      if (this.dataset.index) {
        this.closest("cart-items").updateQuantity(this.dataset.index, 0);
      }
    });
  }
}

Need Help? Contact Us