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
- Add shipping protection to your cart
- Cart normalization, quantity matching, and SP updates
- Ajax Side Cart Integration
- Support post-purchase warranty functionality
- ExtendShopify API reference
- Extend API reference
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.
Themes → Snippets → Add 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>
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
}
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…
- Retrieve an array of physical items from the cart using the
ExtendShopify.spCartMapper
methodconst mappedCartItems = ExtendShopify.spCartMapper(cart.items)
- Check if the shipping protection element is already rendered by using the
Extend.shippingProtection._instance
method- If rendered, we update
Extend.shippingProtection.update({ items: mappedCartItems })
- Otherwise, we create the
shippingProtectionOffer
element and prepend/append to the container that houses the checkout and subtotal elements- Render shipping protection element using
Extend.shippingProtection.render
- Render shipping protection element using
- If rendered, we update
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 cart: Whenever the quantity of a product with a warranty associated with it is increased, the quantity of the extended warranty sku associated with it will also increase to match.
- Unbalanced cart: Whenever the quantity of a product with a warranty associated with it is increased, the quantity of the extended warranty sku will remain the same, and it is up to the shopper to decide if he or she wants to add warranties to protect those new products.
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:
- Make an API call to Shopify to get the most updated cart object
- Reassign the cart variable to the
newCart
object you get from the callback of the API call - Call the function where you wrapped your Extend cart integration script in
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.
Themes → Snippets → Add 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.
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.
Disabling Links to Warranty Product Page
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 %}
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
- Add plan to cart (
#addPlanToCart
) - Normalize and Update SP lineItems(
#updateExtendLineItems
) - Handle Add to Cart(
#handleAddToCart
) - Warranty already in cart(
#warrantyAlreadyInCart
) - Shipping protection already in cart(
#shippingProtectionInCart
) - Shipping Protection Cart Mapper(
#spCartMapper
)
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)
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:
- 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.
- 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.
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:
- Is there physical items in cart
- Is the subtotal of the physical items > $1 or < $15,000
- Is the shipping protection element already rendered
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
}