Overview


If you’re seeing this guide, you have been redirected from the main integration doc and have opted to integrate shipping protection as well. You don’t need to follow that previous doc at this point. You will complete your integration from this document moving forward. You should already have your store setup with Extend, added the scripts, and added the product offers and modal. Integrating with the Extend SDK will allow you to display offers and sell extended warranty contracts and shipping protection in your online store. This guide will walk you through how to do the following:

Add extended warranties to your cart


The cart offer is the last chance your shoppers have to add an extended warranty before they checkout. Here you can display an offer button next to each eligible product in the cart that does not already have a protection plan associated with it.

Cart offer example:

Add the Extend cart offer element


Add an HTML element where you would like to place the Extend cart offer buttons. We recommend placing the element directly below each product in the cart. In most cases, that is in the cart.liquid or the main-cart-items.liquid file.

You need to add this button under each product in the cart that does not have a warranty. Find where the cart items are being iterated on in the template. Then set the quantity and variantId of the product to the cart offer div data attributes:

<div
  id="extend-cart-offer"
  data-extend-variant="{{ item.variant.id }}"
  data-extend-quantity="{{ item.quantity }}"
></div>

Verify that the div has been added to the correct spot by temporarily adding some text, saving, and previewing your cart page. Once you confirm that the text is showing in the correct spot, make sure to remove it!

You also need to verify that the quantity and variantId are being passed into the cart offer div correctly. In your preview, navigate to your cart and inspect the page. You won’t be able to see the Extend cart offer buttons on the page, but you should see the HTML element.

Create custom cart integration snippet

Inside your Shopify theme code editor create a new snippet called extend-cart-integration. This is where you will call the Extend APIs to handle adding warranties to the cart.

ThemesSnippetsAdd a new snippet

Render the snippet in the template where you added your Extend cart offer div (likely your cart page template file).

To ensure this snippet only runs when the Extend SDK is properly initialized, add the following code:

<script>
  if (window.Extend && window.ExtendShopify && window.Extend.shippingProtection) {
    // cart integration code goes here
  }
</script>
Add two helper functions: findAll and hardRefresh. findAll will be used to select all of the cart offer divs on cart page. hardRefesh ensures we do not run into any HTML caching issues when your cart page is refreshed after updates the cart have been detected.
function findAll(element) {
  const { slice } = Array.prototype
  const items = document.querySelectorAll(element)
  return items ? slice.call(items, 0) : []
}

function hardRefresh() {
  location.href = location.hash
    ? location.href.substring(0, location.href.indexOf('#'))
    : location.href
}
Important Note: By using hardRefresh we guarantee that all cart line item quantity updates are always reflected in the DOM across all browsers. As you will see below, this function will be invoked after adding a plan to the cart via a cart offer and after cart normalization updates have been made to the cart.

Gotcha: In order to hard refresh appropriately across all browsers, we need to use location.href = location.href, which does not work if there is a hash tag # in the URL. The hardRefresh function above safely strips hash tags from the URL. This means when the page reloads, the user will always land at the top of the page.

Render cart offer buttons

Before we start adding the logic to render the cart offer buttons, lets create a new function called initCartOffers which we will be called at the bottom of our script.

Call the findAll helper method we added in the last step to find all the Extend cart offer divs. Here you need to pass in the ID of the Extend cart offer element (#extend-cart-offer).

As you iterate through each item, pull out the variantId and the quantity from the #extend-cart-offer div data attributes.

findAll('#extend-cart-offer').forEach(function(el) {
  var variantId = el.getAttribute('data-extend-variant')
  var quantity = el.getAttribute('data-extend-quantity')
  //Continued below...

Use the warrantyAlreadyInCart() function to determine if you should show the offer button.

You can access the Shopify cart object by declaring this variable at the top of your page:

var cart = {{ cart | json }}
if (ExtendShopify.warrantyAlreadyInCart(variantId, cart.items)) {
  return
}

Then render the cart offer buttons using the Extend.buttons.renderSimpleOffer() function.

Extend.buttons.renderSimpleOffer(el, {
  referenceId: variantId,
  onAddToCart: function(options) {
    ExtendShopify.addPlanToCart(
      {
        plan: options.plan,
        product: options.product,
        quantity: quantity,
      },
      function(err) {
        // an error occurred
        if (err) {
          return
        } else {
          // Effectively hard reloads the page; thus updating the cart
          hardRefresh()
          // For ajax carts invoke your cart refresh function
        }
      },
    )
  },
})

ExtendShopify.addPlanToCart also takes a callback that can be used to refresh the cart to reflect the recently added warranty.

function (err) {
  // an error occurred
  if (err) {
    return
  } else {
    // Effectively hard reloads the page; thus updating the cart
    hardRefresh()
  }
}

Your extend-cart-integration.liquid snippet should look like this

<script>
  function findAll(element) {
    const { slice } = Array.prototype
    const items = document.querySelectorAll(element)
    return items ? slice.call(items, 0) : []
  }

  function hardRefresh() {
  location.href = location.hash
    ? location.href.substring(0, location.href.indexOf('#'))
    : location.href
  }

    if (window.Extend && window.ExtendShopify && window.Extend.shippingProtection) {
          var cart = {{ cart | json }}

      function initCartOffers() {
        normalizeCartSP() //This will be explained on the next steps

        findAll('#extend-cart-offer').forEach(function(el) {
          const variantId = el.getAttribute('data-extend-variant')
          const quantity = el.getAttribute('data-extend-quantity')

          if (ExtendShopify.warrantyAlreadyInCart(variantId, cart.items)) {
            return
          }

          Extend.buttons.renderSimpleOffer(el, {
            referenceId: variantId,
            onAddToCart(options) {
              ExtendShopify.addPlanToCart(
                {
                  plan: options.plan,
                  product: options.product,
                  quantity,
                },
                function(err) {
                  // an error occurred
                  if (err) {
                  } else {
                    hardRefresh()
                  }
                },
              )
            },
          })
        })
      }

      // Initial cart offers
      initCartOffers()
    }
    </script>

Verify the cart offer buttons are rendering correctly by previewing your theme and going to your cart page that has an active and enabled product in it. You should see the Extend cart offer button in the cart, and when you click it, it should launch the offer modal. When a shopper clicks this offer button, the modal described in the previous section will launch, and the shopper will be able to select which warranty plan he or she would like to purchase.

Add shipping protection to your cart


Extend Shipping Protection can be added to a merchant’s store exclusive of Extend Product Protection or alongside it. By default it should be displayed on the cart page. To display the Extend Shipping Protection offer on a different page please speak with someone from the Extend Solutions Engineering team.

Cart offer example:

Add the Extend shipping protection cart offer element


Add an HTML element where you would like to place the Extend shipping protection cart offer buttons. We recommend placing the element directly above the subtotal and checkout buttons. In most cases, that is in the cart.liquid or the main-cart-footer.liquid file.

<div id="extend-shipping-offer"></div>

Remove shipping protection line item in cart


Whenever we add shipping protection, a line item will be added to cart. This isn’t a great UI/UX and we want to visually remove that from the cart. The only way we want to know that we have shipping protection is through the checkbox on the offer element above the cart.

First we need to find the cart item element. This is typically found in the cart.liquid or the main-cart-items.liquid file. You can tell you found the cart item element when you see some type of looping logic that goes over every item in cart and the element in question is typically a <tr> element.

To remove shipping protection line items, we need to conditionally check if the line item in cart has the title of Extend Shipping Protection Plan and set the styling of that item to display:none;.

{% if item.title contains 'Extend Shipping Protection Plan'%} style="display: none;"{% endif %}

Render shipping protection offer in cart


Create two variables at the top of the snippet. This will be used to construct the shipping protection offer element.

// This is the ID that will be assigned to the Extend Shipping Protection Offer
const shippingProtectionOfferId = 'extend-shipping-offer'

// This is where the shipping protection offer will be PREPENDED (Switch to append or insertBefore as needed)
const shippingProtectionContainer = '.cart__blocks'

Create a new function called renderOrUpdateSP. This function will be used on page load to render or update the shipping protection offer element.

Inside this function, we’re going to…

  1. Retrieve an array of physical items from the cart using the ExtendShopify.spCartMapper method
    1. const mappedCartItems = ExtendShopify.spCartMapper(cart.items)
  2. Check if the shipping protection element is already rendered by using the Extend.shippingProtection._instance method
    1. If rendered, we update Extend.shippingProtection.update({ items: mappedCartItems })
    2. Otherwise, we create the shippingProtectionOffer element and prepend/append to the container that houses the checkout and subtotal elements
      1. Render shipping protection element using Extend.shippingProtection.render
function renderOrUpdateSP() {
  // Retrieves an array of physical items from the cart
  const mappedCartItems = ExtendShopify.spCartMapper(cart.items)

  // Check if the shipping protection element is already rendered, if so, update the price
  if (Extend.shippingProtection._instance !== null) {
    Extend.shippingProtection.update({ items: mappedCartItems })
  } else {
    // Creating the shipping protection element and placing it in the correct container
    const shippingProtectionOffer = document.createElement('div')
    shippingProtectionOffer.id = shippingProtectionOfferId
    document.querySelector(shippingProtectionContainer).prepend(shippingProtectionOffer)

    const isShippingProtectionInCart = ExtendShopify.shippingProtectionInCart(cart.items)
    // Renders the shipping protection element
    Extend.shippingProtection.render({
      selector: '#extend-shipping-offer',
      items: mappedCartItems,
      isShippingProtectionInCart,
      onEnable(quote) {
        ExtendShopify.addSpPlanToCart({
          quote,
          cart,
          callback(err, resp) {
            if (err) {
            } else {
              hardRefresh()
            }
          },
        })
      },
      onDisable(quote) {
        ExtendShopify.updateSpPlanInCart({
          action: 'remove',
          cart,
          callback(err, resp) {
            // an error occurred
            if (err) {
            } else if (resp.isUpdated) hardRefresh()
          },
        })
      },
      onUpdate(quote) {
        ExtendShopify.updateSpPlanInCart({
          action: 'update',
          cart,
          callback(err, resp) {
            // an error occurred
            if (err) {
            } else if (resp.isUpdated) hardRefresh()
          },
        })
      },
    })
  }
}

By the end of it, your snippet should look like this.

<script>
  // This is the ID that will be assigned to the Extend Shipping Protection Offer
  const shippingProtectionOfferId = 'extend-shipping-offer'

  // This is where the shipping protection offer will be PREPENDED (Switch to append or insertBefore as needed)
  const shippingProtectionContainer = '.cart__blocks'

  function findAll(element) {
    const { slice } = Array.prototype
    const items = document.querySelectorAll(element)
    return items ? slice.call(items, 0) : []
  }

  function hardRefresh() {
    location.href = location.hash
      ? location.href.substring(0, location.href.indexOf('#'))
      : location.href
  }

  if (window.Extend && window.ExtendShopify && window.Extend.shippingProtection) {
    var cart = {{ cart | json }}

    function renderOrUpdateSP() {
      const mappedCartItems = ExtendShopify.spCartMapper(cart.items)

      // Check if the shipping protection element is already rendered, if so, update the price
      if (Extend.shippingProtection._instance !== null) {
        Extend.shippingProtection.update({ items: mappedCartItems })
      } else {
        // Creating the shipping protection element and placing it in the correct container
        const shippingProtectionOffer = document.createElement('div')
        shippingProtectionOffer.id = shippingProtectionOfferId
        document.querySelector(shippingProtectionContainer).prepend(shippingProtectionOffer)

        const isShippingProtectionInCart = ExtendShopify.shippingProtectionInCart(cart.items)
        Extend.shippingProtection.render({
          selector: '#extend-shipping-offer',
          items: mappedCartItems,
          isShippingProtectionInCart,
          onEnable(quote) {
            ExtendShopify.addSpPlanToCart({
              quote,
              cart,
              callback(err, resp) {
                if (err) {
                } else {
                  hardRefresh()
                }
              },
            })
          },
          onDisable(quote) {
            ExtendShopify.updateSpPlanInCart({
              action: 'remove',
              cart,
              callback(err, resp) {
                // an error occurred
                if (err) {
                } else if (resp.isUpdated) hardRefresh()
              },
            })
          },
          onUpdate(quote) {
            ExtendShopify.updateSpPlanInCart({
              action: 'update',
              cart,
              callback(err, resp) {
                // an error occurred
                if (err) {
                } else if (resp.isUpdated) hardRefresh()
              },
            })
          },
        })
      }
    }

    function initCartOffers() {
      normalizeCartSP() //This will be explained on the next steps

      findAll('#extend-cart-offer').forEach(function(el) {
        const variantId = el.getAttribute('data-extend-variant')
        const quantity = el.getAttribute('data-extend-quantity')

        if (ExtendShopify.warrantyAlreadyInCart(variantId, cart.items)) {
          return
        }

        Extend.buttons.renderSimpleOffer(el, {
          referenceId: variantId,
          onAddToCart(options) {
            ExtendShopify.addPlanToCart(
              {
                plan: options.plan,
                product: options.product,
                quantity,
              },
              function(err) {
                // an error occurred
                if (err) {
                } else {
                  window.location.reload()
                }
              },
            )
          },
        })
      })
    }

    // Initial cart offers
    initCartOffers()
  }
</script>

Cart normalization, quantity matching, and SP updates


As part of the checkout process, customers often update product quantities in their cart. The cart normalization with sp updating(updateExtendLineItems), feature will automatically adjust the quantity of Extend protection plans and update the current shipping protection plan in cart. If a customer increases or decreases the quantity of products, the quantity for the related warranties in the cart should increase or decrease as well. In addition, if a customer has completely removed a product from the cart, any related warranties should be removed from the cart so the customer does not accidentally purchase a protection plan without a product. On top of that, the shipping protection price should change depending on the subtotal of the physical items in cart.

ExtendShopify.updateExtendLineItems({
  balanceCart: true,
  callback(err, data) {
    hardRefresh()
    if (!err && data && (data.updates || data.additions)) {
      renderOrUpdateSP()
    }
  },
})

ExtendShopify.updateExtendLineItems will return a promise that will give you the data and err object to check if the cart needs to be normalized. If the data object exists and the data.updates or data.additions is truthy, you will then call your function to refresh the cart page. Typically reloading the page will work for most Shopify cart pages.

Create a function called normalizeCartSP and this function will be called within the initCartOffers method.

function normalizeCartSP() {
  ExtendShopify.updateExtendLineItems({
    balanceCart: true,
    callback(err, data) {
      hardRefresh()
      if (!err && data && (data.updates || data.additions)) {
        renderOrUpdateSP()
      }
    },
  })
}

By this point, you should have a fully integrated store with working cart page updates using updateExtendLineItems. Your cart snippet should look like this.

<script>
  // This is the ID that will be assigned to the Extend Shipping Protection Offer
  const shippingProtectionOfferId = 'extend-shipping-offer'

  // This is where the shipping protection offer will be PREPENDED (Switch to append or insertBefore as needed)
  const shippingProtectionContainer = '.cart__blocks'

  function findAll(element) {
    const { slice } = Array.prototype
    const items = document.querySelectorAll(element)
    return items ? slice.call(items, 0) : []
  }

  function hardRefresh() {
    location.href = location.hash
      ? location.href.substring(0, location.href.indexOf('#'))
      : location.href
  }

  if (window.Extend && window.ExtendShopify && window.Extend.shippingProtection) {
    var cart = {{ cart | json }}

    function renderOrUpdateSP() {
      const mappedCartItems = ExtendShopify.spCartMapper(cart.items)

      // Check if the shipping protection element is already rendered, if so, update the price
      if (Extend.shippingProtection._instance !== null) {
        Extend.shippingProtection.update({ items: mappedCartItems })
      } else {
        // Creating the shipping protection element and placing it in the correct container
        const shippingProtectionOffer = document.createElement('div')
        shippingProtectionOffer.id = shippingProtectionOfferId
        document.querySelector(shippingProtectionContainer).prepend(shippingProtectionOffer)

        const isShippingProtectionInCart = ExtendShopify.shippingProtectionInCart(cart.items)
        Extend.shippingProtection.render({
          selector: '#extend-shipping-offer',
          items: mappedCartItems,
          isShippingProtectionInCart,
          onEnable(quote) {
            ExtendShopify.addSpPlanToCart({
              quote,
              cart,
              callback(err, resp) {
                if (err) {
                } else {
                  hardRefresh()
                }
              },
            })
          },
          onDisable(quote) {
            ExtendShopify.updateSpPlanInCart({
              action: 'remove',
              cart,
              callback(err, resp) {
                // an error occurred
                if (err) {
                } else if (resp.isUpdated) hardRefresh()
              },
            })
          },
          onUpdate(quote) {
            ExtendShopify.updateSpPlanInCart({
              action: 'update',
              cart,
              callback(err, resp) {
                // an error occurred
                if (err) {
                } else if (resp.isUpdated) hardRefresh()
              },
            })
          },
        })
      }
    }

    function normalizeCartSP() {
      ExtendShopify.updateExtendLineItems({
        balanceCart: true,
        callback(err, data) {
          hardRefresh()
          if (!err && data && (data.updates || data.additions)) {
            renderOrUpdateSP()
          }
        },
      })
    }

    function initCartOffers() {
      normalizeCartSP() //This will be explained on the next steps

      findAll('#extend-cart-offer').forEach(function(el) {
        const variantId = el.getAttribute('data-extend-variant')
        const quantity = el.getAttribute('data-extend-quantity')

        if (ExtendShopify.warrantyAlreadyInCart(variantId, cart.items)) {
          return
        }

        Extend.buttons.renderSimpleOffer(el, {
          referenceId: variantId,
          onAddToCart(options) {
            ExtendShopify.addPlanToCart(
              {
                plan: options.plan,
                product: options.product,
                quantity,
              },
              function(err) {
                // an error occurred
                if (err) {
                } else {
                  window.location.reload()
                }
              },
            )
          },
        })
      })
    }

    // Initial cart offers
    initCartOffers()
  }
</script>

Balanced vs unbalanced carts

Now that you have the normalize function in place, you need to decide if you want a balanced or unbalanced cart.

Balanced and unbalanced carts can be toggled with the balance: true/false property

Ajax cart normalization

Not using an AJAX cart? Feel free to skip ahead to the Styling section.

If you are using an Ajax cart, the page does not reload whenever an item’s quantity is updated. This means that in order to normalize an Ajax cart, you need to identify when the quantity of an item changes and then run the ExtendShopify.updateExtendLineItems function.

Define the cart integration code in a function, and then call that function at the bottom of your script. You should already have the initCartOffers function created from a few steps back.

Now dispatch an event whenever an item in the cart gets updated. To do this, first add an eventListener in your cart integration script.

Inside the eventListener do the following:

window.addEventListener('refreshCart', function() {
  fetch('/cart.js')
    .then(data => {
      return data.json()
    })
    .then(newCart => {
      cart = newCart
      initCartOffers()
    })
})

This is what the extend-cart-integration snippet should look like…

<script>
  // This is the ID that will be assigned to the Extend Shipping Protection Offer
  const shippingProtectionOfferId = 'extend-shipping-offer'

  // This is where the shipping protection offer will be PREPENDED (Switch to append or insertBefore as needed)
  const shippingProtectionContainer = '.cart__blocks'

  function findAll(element) {
    const { slice } = Array.prototype
    const items = document.querySelectorAll(element)
    return items ? slice.call(items, 0) : []
  }

  function hardRefresh() {
    location.href = location.hash
      ? location.href.substring(0, location.href.indexOf('#'))
      : location.href
  }

  if (window.Extend && window.ExtendShopify && window.Extend.shippingProtection) {
    const cart = {{ cart | json }}

    function renderOrUpdateSP() {
      const mappedCartItems = ExtendShopify.spCartMapper(cart.items)

      // Check if the shipping protection element is already rendered, if so, update the price
      if (Extend.shippingProtection._instance !== null) {
        Extend.shippingProtection.update({ items: mappedCartItems })
      } else {
        // Creating the shipping protection element and placing it in the correct container
        const shippingProtectionOffer = document.createElement('div')
        shippingProtectionOffer.id = shippingProtectionOfferId
        document.querySelector(shippingProtectionContainer).prepend(shippingProtectionOffer)

        const isShippingProtectionInCart = ExtendShopify.shippingProtectionInCart(cart.items)
        Extend.shippingProtection.render({
          selector: '#extend-shipping-offer',
          items: mappedCartItems,
          isShippingProtectionInCart,
          onEnable(quote) {
            ExtendShopify.addSpPlanToCart({
              quote,
              cart,
              callback(err, resp) {
                if (err) {
                } else {
                  hardRefresh()
                }
              },
            })
          },
          onDisable(quote) {
            ExtendShopify.updateSpPlanInCart({
              action: 'remove',
              cart,
              callback(err, resp) {
                // an error occurred
                if (err) {
                } else if (resp.isUpdated) hardRefresh()
              },
            })
          },
          onUpdate(quote) {
            ExtendShopify.updateSpPlanInCart({
              action: 'update',
              cart,
              callback(err, resp) {
                // an error occurred
                if (err) {
                } else if (resp.isUpdated) hardRefresh()
              },
            })
          },
        })
      }
    }

    function normalizeCartSP() {
      ExtendShopify.updateExtendLineItems({
        balanceCart: true,
        callback(err, data) {
          hardRefresh()
          if (!err && data && (data.updates || data.additions)) {
            renderOrUpdateSP()
          }
        },
      })
    }

    function initCartOffers() {
      normalizeCartSP()

      findAll('#extend-cart-offer').forEach(function(el) {
        const variantId = el.getAttribute('data-extend-variant')
        const quantity = el.getAttribute('data-extend-quantity')

        if (ExtendShopify.warrantyAlreadyInCart(variantId, cart.items)) {
          return
        }

        Extend.buttons.renderSimpleOffer(el, {
          referenceId: variantId,
          onAddToCart(options) {
            ExtendShopify.addPlanToCart(
              {
                plan: options.plan,
                product: options.product,
                quantity,
              },
              function(err) {
                // an error occurred
                if (err) {
                } else {
                  window.location.reload()
                }
              },
            )
          },
        })
      })
    }

    // Initial cart offers
    initCartOffers()

    // Ajax Cart Event Listener
    window.addEventListener('refreshCart', function() {
      fetch('/cart.js')
        .then(data => {
          return data.json()
        })
        .then(newCart => {
          cart = newCart
          initCartOffers()
        })
    })
  }
</script>

Now that you have the eventListener initialized, you need to find where in your code to dispatch a custom event. Find where in your Shopify theme the quantity of a cart item is updated and dispatch an event back to the cart integration script to pull in the new Shopify cart object.

Example: In the example below, the quantity of a cart item is being updated from the updateQuantity() function in the cart.js file:

Ajax Side Cart Integration


Ajax side-carts are quite common, and the integration is similar to that of a regular Ajax cart.

Add the Extend cart offer element


Add an HTML element where you would like to place the Extend cart offer buttons. We recommend placing it directly below each product in the cart. Typically this can be found in the ajax-cart-template.liquid file or in another similar template file.

You need to add this element under each product in the cart that does not have a warranty. Find where the cart items are being iterated on in the template. Then set the quantity and variantId of the product to the cart offer div data attributes:

<div
  id="extend-cart-offer"
  data-extend-variant="{{ variantId }}"
  data-extend-quantity="{{ itemQty }}"
></div>

Create custom ajax side-cart integration snippet


Inside your Shopify theme code editor create a new snippet called extend-ajax-side-cart-integration. This is where you will call the Extend APIs to handle displaying offers on the product page and adding the warranties to the cart.

ThemesSnippetsAdd a new snippet

Render ajax side-cart offer buttons


Copy the code from the extend-cart-integration snippet that you created and paste it into the extend-ajax-side-cart-integration snippet. This will render the cart offer buttons in your ajax-side cart.

Then add an eventListener to dispatch events from your themes.js file. This will help rerun the script whenever a product is added or the cart needs to be normalized.

window.addEventListener('refreshSideCart', function() {
  $.getJSON('/cart.js', function(newCart) {
    cart = newCart
    initializeCartOffer()
  })
})

Adding a warranty from an ajax side-cart


Whenever an extended warranty is added from the ajax side-cart, you need to rebuild your ajax side-cart with the new Shopify cart object as well as call the ajax-side-cart-integration script. This will add the warranty to the cart as well as remove the cart offer button from the product in your side-cart.

Important Note: You can add the eventListener to the ajax-side-cart-integration script and dispatch custom events from your theme’s javascript file. This will allow you to rerun the snippet whenever a products quantity is changed or if the product is removed.
window.dispatchEvent(new CustomEvent('cartItemUpdated'))

If you plan on enabling support for customers who use Internet Explorer 11, the above code would need to be rewritten in the following way, since CustomEvent would not be defined for them:

function createCustomEvent(eventName, params) {
  // If user is on a modern browser, use its implementation of CustomEvent
  if (window.CustomEvent) {
    return new CustomEvent(eventName, params)
  }

  var options = params || { bubbles: false, cancelable: false, detail: null }
  var evt = document.createEvent('CustomEvent')
  evt.initCustomEvent(event, options.bubbles, options.cancelable, options.detail)
  return evt
}

window.dispatchEvent(createCustomEvent('cartItemUpdated'))

In the example below we add our eventListener to allow us to run the function that builds the ajax side-cart. This eventListener will be ran from the custom dispatched event we sent in the previous example.

window.addEventListener('cartItemUpdated', function() {
  Extend.buttons.instance('#extend-cart-offer').destroy()

  $.getJSON('/cart.js', function(cart) {
    cartUpdateCallback(cart)
  })
})

Once the ajax side-cart is rebuilt, you may also need to dispatch an event back to the ajax-side-cart-integration snippet to allow for the script to be run again.

Ajax side-cart normalization


In order to normalize the ajax side‐cart, find where in your theme the ajax side-cart is rebuilt/updated when the quantity of a product is changed and dispatch a custom event to the same eventListener that was setup in your ajax-side‐cart-integration snippet.

Example:

Once our script is rerun and we determine we need to normalize the cart, we will dispatch an event to the extend-side-cart-integration file to allow for the ajax side-cart to be rebuilt/refreshed with the new Shopify cart object.

Styling the Warranty in the Cart

Most cart templates will iterate through a product’s details and display them on the cart page. They will also link to the product’s page from the thumbnail image and from the product title. In the case of the warranty, we recommend hiding the meta-data and disabling the links to the warranty product page so a customer cannot purchase a warranty without a matching product.


First, make sure you have an Extend warranty added to your cart. Next, navigate to the file where cart items are created. Typically, this will be the cart-template.liquid file. Within the file, find the line of code that iterates through the items in the cart.

{% for item in cart.items %}

or

{% for line_item in cart.items %}
Important Note: Sometimes you will have a separate file for the individual cart items. In that case, you will want to make changes to that file.

Within that for loop, find the elements where class=item-image and class=item-title. The class names might be slightly different, i.e. they may have cart- prepended. Within the opening tags of both of those elements, add the following line of code:


Or, if the element is referred to as a line_item:


This will conditionally disable the links on the warranty’s thumbnail image and its title. Reload the page and ensure the links are disabled!

Hiding Warranty Meta-Data


Within the same file, typically below the item-title element, you’ll see a section of code that iterates through the product’s options. That will look something like the following (depending on how the individual cart items are named):

{% for option in item.product.options %}

Above that line, there will be an conditional to check whether the product options should be displayed. Often it will look lik e the following:

{% unless line_item.variant.title contains 'Default'}

Add an or statement to the conditional to check whether it’s an Extend warranty:

{% unless line_item.variant.title contains 'Default' or line_item.vendor == 'Extend' %}

Finally, you’ll see a section of code that lists the products properties. Look for an element with a class name that starts with product-details. Within the opening tag of that element, you’ll see a conditional that looks something like the following:

{%if property_size == 0%} hide{% endif %}

Add or statements to the conditional to check whether the property is of type Ref, Extend.LeadToken or Extend.LeadQuantity:

{%if property_size == 0 or p.first == 'Ref' or p.first == 'Extend.LeadToken' or p.first == 'Extend.LeadQuantity' %} hide{% endif %}

The final product should look something like the following:

<tbody>
  {% for line_item in cart.items %}
  <tr class="cart-item" data-id="{{ line_item.id }}">
    <td class="image">
      <div
            class="item-image"
            {% if line_item.vendor == 'Extend' %}style="pointer-events: none;" {% endif %}
            >
        <a href="{{ line_item.url }}">
          <img src="{{ line_item.image | img_url: 'small' }}" alt="{{ line_item.title }}" />
        </a>
      </div>
    </td>
    <td class="item-name">
      <div
            class="item-title"
            {% if line_item.vendor == 'Extend' %} style="pointer-events: none;" {% endif %}
            >
        <a href="{{ line_item.url }}">
          <span class="item-name">{{ line_item.product.title }}</span>
        </a>
        {% unless line_item.variant.title contains 'Default' or line_item.vendor == 'Extend' %}
          <div class="wrap-item-variant">
            {% for option in line_item.product.options %}
              <span class="item-variant">{{ option }}: <span class="variant-title">{{ line_item.variant.options[forloop.index0] }}</span></span>
            {% endfor %}
          </div>
          {% endunless %}
          {% assign property_size = line_item.properties | size %}
          {% if property_size > 0 %}
          {%- for p in line_item.properties -%}
          {%- unless p.last == blank -%}
        <li class="product-details__item product-details__item--property {%if property_size == 0 or p.first == 'Ref' or p.first == 'Extend.LeadToken' or p.first == 'Extend.LeadQuantity' %} hide{% endif %}" data-cart-item-property>
        <!-- NO FURTHER EDITS TO THIS SECTION OF CODE NEEDED -->
      </div>
    </td>
  </tr>
</tbody>

That’s it! Give your page a refresh and ensure you can no longer see the warranty Reference Number and Title.

Final review

Congratulations, you have finished integrating the Extend Shipping Protection into your store!


ExtendShopify API reference


Introduction

Welcome to the ExtendShopify API reference! We’re happy that you’ve decided to partner with us and leverage our Shopify SDK. This reference details the functions available to you via the ExtendShopify SDK interface and should be used in conjunction with our Extend Shopify Integration Guide.

Table of Contents

ExtendShopify.addPlanToCart(options: AddToCartOptions, callback: function)


This function adds an Extend warranty plan to the cart. However, it should only be used within a callback that is provided to an Extend base SDK function that returns an Extend Plan and Product. Important Note: If you are looking to add a product and its associated warranty to the cart, please see #handleAddToCart instead.

ExtendShopify.addPlanToCart({ plan, product, quantity }, callback)
Example use case: Extend.buttons.renderSimpleOffer and Extend.aftermarketModal.open

Attributes

Attribute Data type Description
addToCartOptions
required
object AddToCartOptions
callback
optional
function Callback function that will be executed after the item is added to the cart

AddToCartOptions Object

Attribute Data type Description
plan
required
object Extend plan object to be added to the cart
product
required
object Product associated with the warranty plan
quantity
optional
number Number of plans to be added to the cart (defaults to one)
leadToken
optional
string Token used for post purchase offers

Interfaces

interface addPlanToCartProps {
  opts: AddToCartOpts
  callback?: Callback
}
interface AddToCartOpts {
  plan: PlanSelection
  product: Product
  quantity: number
  leadToken?: string
}

ExtendShopify.updateExtendLineItems(options: UpdateExtendLineItemsOptions)


This function does two things:

  1. Accepts and updates the Shopify cart object to ensure that the line item quantity of a warranty is not greater than the line item quantity of its associated product and returns an object containing the updated cart and cart updates.
  2. Updates shipping protection plans depending on the subtotal of physical items currently in cart.

Therefore, this function should be executed every time the cart is updated in order to ensure a user cannot buy a warranty for a product not in the cart. While optional, a callback should almost always be passed as a second argument. This callback will be executed after the cart normalizes and should therefore be used to update the quantity input selectorson the page with their updated values, typically via a hard refresh.

Use case: Cart normalization
ExtendShopify.updateExtendLineItems({
  balanceCart: true,
  callback(err, data) {
    hardRefresh()
    if (!err && data && (data.updates || data.additions)) {
      renderOrUpdateSP()
    }
  },
})

Attributes

Attribute Data type Description
UpdateExtendLineItemsOptions
required
object NormalizeCartOptions

UpdateExtendLineItemsOptions Object

Attribute Data type Description
balance
boolean When set to true warranty quantity will equal the associated product quantity
callback
function Callback function that will be executed after the normalizeCart function is invoked (Typically refreshes the cart)

Interfaces

interface UpdateExtendLineItemsOpts {
  balanceCart: boolean
  callback: Callback
}
interface UpdateExtendLineItemsResult {
  additions: CartSPAddition | null
  updates: CartUpdates | null
  cart: Cart
}

UpdateExtendLineItems response object

Attributes

Attribute Data type Description
additions object or null Object containing the updated shipping protection plan
updates object or null Object containing the updated variantId’s and their updated quantities
cart object Normalized Cart Object

ExtendShopify.handleAddToCart(element: string, options: HandleAddToCartOpts)


This function will check the Extend offer buttons for a selected plan. If a plan is selected, the function will add the plan to the cart and then execute a callback provided in the HandleAddToCartOpts object. If a plan is NOT selected, the function will check the HandleAddToCartOpts to determine whether or not to render the Extend offer modal. The done callback provided will be executed after the user adds a warranty, clicks ‘No, thanks’ or closes the modal. Use this callback to call your stores add to Cart function or your product submit form.

ExtendShopify.handleAddToCart('#extend-offer', {
  quantity: quantity,
  modal: true,
  done: function() {
    // callback logic to add the product to the cart
  },
})

Attributes

Attribute Data type Description
element
required
string The ID of the container element used to render the Extend offer buttons
HandleAddToCartOpts
required
object HandleAddToCartOpts

HandleAddToCartOpts Object

Attribute Data type Description
modal
optional
boolean Determines whether to render the modal if no plan is selected (defaults to true)
quantity
optional
integer Quantity of warranties to add to the cart (defaults to one)
done
optional
function Callback executed after the warranty business logic

Interfaces

interface HandleAddToCart {
  element: string | Element
  opts: HandleAddToCartOpts
}
interface HandleAddToCartOpts {
  modal?: boolean
  quantity?: number
  done?: Callback
}

ExtendShopify.warrantyAlreadyInCart(variantId: string, cartItems: CartItem[])


This function accepts a Shopify product variantId and the Shopify cart items array. The function iterates through the Shopify cart items and returns a boolean indicating if there is already a warranty in the cart for that product variantId. This function is almost always used on the cart page to determine whether or not to render a cart offer button for a line item in the cart.

var cart = {{ cart | json }}

if (ExtendShopify.warrantyAlreadyInCart(variantId, cart.items)) {
  return;
} else {
  // render a cart offer button
}

Attributes

Attribute Data type Description
variantId string This Shopify variantId of the product to be checked for a warranty
cartItems array The array of cart items stored on the Shopify cart items property

ExtendShopify.shippingProtectionInCart(cartItems: CartItem[])


This function accepts a the Shopify cart items array. The function iterates through the Shopify cart items and returns a boolean indicating if there is already a shipping protection item in the cart. This function is almost always used on the cart page to determine whether or not the checkbox in the shipping protection element is enabled.

var cart = {{ cart | json }}

const isShippingProtectionInCart = ExtendShopify.shippingProtectionInCart(cart.items)
// Passed into the shipping protection render method
Extend.shippingProtection.render({
  selector: '#extend-shipping-offer',
  items: mappedCartItems,
  isShippingProtectionInCart,
  ...

Attributes

Attribute Data type Description
cartItems array The array of cart items stored on the Shopify cart items property

ExtendShopify.spCartMapper(cartItems: CartItem[])


This function accepts a the Shopify cart items array. The function iterates through the Shopify cart items and returns an array of items formatted to be passed into the render function which will calculate the correct shipping insurance quote for the cart.

var cart = {{ cart | json }} 

const mappedCartItems = ExtendShopify.spCartMapper(cart.items)
// Passed into the shipping protection render method
Extend.shippingProtection.render({
  selector: '#extend-shipping-offer',
  items: mappedCartItems,
  isShippingProtectionInCart,
  ...

Attributes

Attribute Data type Description
cartItems array The array of cart items stored on the Shopify cart items property

Extend API reference


Introduction

Welcome to the Extend API reference! We’re happy that you’ve decided to partner with us and leverage our Shopify SDK. This reference details the functions available to you via the Extend SDK interface and should be used in conjunction with our Extend Shopify Integration Guide.

Table of Contents

Extend.shippingProtection.render(options: ShippingProtectionRenderInterface)


This function handles rendering the shipping protection offer element above the subtotal/checkout container. The shipping protection element is rendered depending on specific conditions with the cart:

  1. Is there physical items in cart
  2. Is the subtotal of the physical items > $1 or < $15,000
  3. Is the shipping protection element already rendered
Use case: Rendering Shipping Protection Cart Offer
Extend.shippingProtection.render({
  selector: '#extend-shipping-offer',
  items: mappedCartItems,
  isShippingProtectionInCart,
  onEnable(quote) {
    ExtendShopify.addSpPlanToCart({
      quote,
      cart,
      callback(err, resp) {
        if (err) {
        } else {
          hardRefresh()
        }
      },
    })
  },
  onDisable(quote) {
    ExtendShopify.updateSpPlanInCart({
      action: 'remove',
      cart,
      callback(err, resp) {
        // an error occurred
        if (err) {
        } else if (resp.isUpdated) hardRefresh()
      },
    })
  },
  onUpdate(quote) {
    ExtendShopify.updateSpPlanInCart({
      action: 'update',
      cart,
      callback(err, resp) {
        // an error occurred
        if (err) {
        } else if (resp.isUpdated) hardRefresh()
      },
    })
  },
})

Attributes

Attribute Data type Description
ShippingProtectionRenderInterface
required
object NormalizeCartOptions

ShippingProtectionRenderInterface Object

Attribute Data type Description
selector
string The class string name that will be used to render the element
items
array An array of physical items returned from the spCartMapper function
isShippingProtectionInCart
boolean A boolean to determine if there is a shipping protection line item already in cart
onEnable
function Callback function that will be executed after the render function is invoked (Typically refreshes the cart)
onDisable
function Callback function that will be executed after the render function is invoked (Typically refreshes the cart)
onUpdate
function Callback function that will be executed after the render function is invoked (Typically refreshes the cart)

Interfaces

interface ShippingProtectionRenderInterface {
  selector: string
  items: ShippingOffersItem[]
  isShippingProtectionInCart: boolean
  onEnable: (quote) => void
  onDisable: (quote) => void
  onUpdate: (quote) => void
}