Skip to main content

Shopify + One-To-One

note

This is our recommended approach for integrating to Shopify. You may of course modify the code based on your wishes following the general documentation.


img

In order to integrate the Bambuser Call Widget into a Shopify store, you will need to add Bambuser related integration code to your Shopify website. The integration code can be added by adding script tags to your page content or theme codes.

Steps:

  1. Integrate Bambuser Call Widget and Overlay WidgetπŸ”—
  2. Add Conversion Tracking πŸ”—
Compatibility

Due to Shopify's technical limitations, the Cobrowsing, Surf While in Queue and Miniplayer features are not compatible with Shopify stores. You can read more about the compatibility here.

Integration​

Sample integration code​

Below you find a sample integration code that implements:

  • Overlay Widget
  • Provide Product Data
  • Provide Search Data
  • Cart Integration

This sample code works on almost all Shopify stores that have standard product models without any changes required. However, if you have a special product setup, you may need to modify the code to justify that with your case.

Note that before you will have to modify your embed code a bit in order to make it work as you want

1. Organization ID (mandatory)​

For the Call Widget initiation to work, you will need to enter your own organization ID (orgId). That means that you need to have access to Bambuser Dashboard.

Your orgId can be found in Bambuser dashboard URL (https://lcx.bambuser.com/o/[orgId]/...)


2. Queue ID (optional)​

Bambuser One-to-One product comes with a built-in queing system. Dashboard admins can configure different queues in Bambuser dashboard. Each queue will get its own unique ID when created.

Each queue will have a Queue ID, which One-to-One Admin can find in Queue Settings within Bambuser Dashboard.

If you want to redirect your end customers to a different queue that the default one, you will need to additionaliy specify that in the below code sample.


3. Embed Source URL (optional)​

Embed source URL is different depending on location of servers that your dashboard is set up on. Note that below sample code is assuming your dashboard is setup on Global Servers.

Use Global servers URL https://one-to-one.bambuser.com/embed.js if the login to your Bambuser dashboard is on following link: https://lcx.bambuser.com/. If that is the case, you do not need to make those changes in the code below.
SAMPLE INTEGRATION CODE
<script>
// Reduces server calls if a product has a crazy number of images.
const MAX_IMAGES_COUNT_ = 20;

// Extracts product handle from the product URL
const SHOPIFY_PRODUCT_URL_HANDLE_REGEX_ = /\/products\/(.[\w\d-+]+)/;

// Strip Html elements
function stripHtml(html) {
let tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
}

// Sometimes image URLs miss the protocol at the beginning
// E.g. '//cdn.shopify.com/s/files/.../image.jpg'
const urlSanitizer_ = (url) => {
if (typeof url === "string") {
if (url.startsWith("//")) return `https:${url}`;
else if (url.toLocaleLowerCase().startsWith("http")) return url;
else console.log(`Not a valid URL: ${url}`);
} else console.log(`Not a valid URL: ${url}`);
return null;
};

const currency = () => {
let cart_currency = document.cookie
.split("; ")
.find((row) => row.startsWith("cart_currency"));
return (cart_currency && cart_currency.split("=")[1]) || "USD";
};

// ====================================== Shopify Ajax API Helper methods ========================================
const shopify = {};

shopify.getProductByUrl = (url) => {
const handle = SHOPIFY_PRODUCT_URL_HANDLE_REGEX_.exec(url);
return fetch("/products/" + handle[1] + ".js", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((resp) => resp.json());
};

shopify.getProductByHandle = (handle) => {
return fetch("/products/" + handle + ".js", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((resp) => resp.json());
};

shopify.getRelatedProducts = (productId) => {
return fetch(window.Shopify.routes.root + "recommendations/products.json?product_id=" + [productId] + "&limit=5")
.then(response => response.json());
}

shopify.getProduct = (ref) => {
if (SHOPIFY_PRODUCT_URL_HANDLE_REGEX_.test(ref)) {
return shopify.getProductByUrl(ref)
.then((productObject) => {
return shopify.getRelatedProducts(productObject.id).then(result => {
productObject["relatedProducts"] = result.products
return productObject;
})
})
} else {
return shopify.getProductByHandle(ref.split(":")[1])
.then((productObject) => {
return shopify.getRelatedProducts(productObject.id).then(result => {
productObject["relatedProducts"] = result.products
return productObject;
})
})
}
};

shopify.predictiveSearch = (query) =>
fetch("/search/suggest.json?q=" + query + "&resources[type]=product", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((res) => res.json());

shopify.addToCart = (itemId) =>
fetch("/cart/add.js", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [{
quantity: 1,
id: itemId.split(":")[0],
}, ],
}),
}).then((resp) => resp.json());

shopify.updateItemInCart = (itemId, quantity) =>
fetch("/cart/update.js", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
updates: {
[itemId.split(":")[0]]: quantity,
},
}),
}).then((resp) => resp.json());

shopify.getCartState = () =>
fetch("/cart.js", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((resp) => resp.json());

// ===================================== Bambuser OneToOneReady Handler ==========================================
function onBambuserOneToOneReady() {
// This method will be invoked when embed.js has loaded and Bambuser
// One-to-One API is available.
// Creating an instance directly will allow to detect connect links
// that will automatically open the Live Meeting overlay on page load.
let oneToOneEmbed = new BambuserOneToOneEmbed({
orgId: "[ORG_ID]",
// queue: '[QUEUE_ID_HERE]', // if not specified, default queue configured from the dashboard will get applied
triggers: [
'smart', // activate the overlay widget
'connect-link' // activate the connect link - Unrelated to OLW, yet mandatory
],
smartVariantOverride: 'Video', // force variant 'Video'. Other options are 'Avatar' and 'Side dock'
popupTimeoutSeconds: '5' // after how many seconds OLW appears on the first time page load (default = 60)
});

//------------------------------------ Provide Product Data ----------------------------------
oneToOneEmbed.on("provide-product-data", (event) => {
event.products.forEach(({
ref,
id
}) => {
shopify.getProduct(ref).then((item) => {
oneToOneEmbed.updateProduct(id, (productFactory) =>
productFactory
.currency(currency())
.locale("en-US")
.product((detailFactory) =>
detailFactory
.attributes((attribute) => {
if (item.hasOwnProperty("options")) {
return item.options.map((attr) =>
attribute(attr.name)
.name(attr.name)
.options((option) =>
attr.values.map((optionName) =>
option(optionName).name(optionName)
)
)
);
} else {
return [];
}
})
.description(stripHtml(item.description))
.name(item.title)
.sku(item.id + ":" + item.handle)
.variations((variationFactory) =>
item.variants.map((variation) =>
variationFactory()
.name(item.title + ` (${variation.title})`)
.sku(variation.id + ":" + item.handle)
.inStock(variation.available)
.imageUrls([
// Adding the featured image of the chosen variation
// (if existed) at the begining of the images array
...(variation.featured_image ? [variation.featured_image.src] : []),
// Adding product imgaes
...item.images
.slice(0, MAX_IMAGES_COUNT_ - 1)
.map((url) => urlSanitizer_(url))
.filter((url) => typeof url === "string")
.filter((image) => image !== ((variation.featured_image !== null) ? variation.featured_image.src : null))
])
.price((priceFactory) =>
priceFactory
.original(variation.compare_at_price / 100)
.current(variation.price / 100)
)
.attributes((attribute) =>
item.options.map((attr) =>
attribute(
attr.name,
variation["option" + attr.position]
)
)
)
.relatedProducts((relatedProductFactory) =>
item.relatedProducts.map((relatProd) =>
relatedProductFactory()
.title(relatProd.title)
.sku(relatProd.id + ":" + relatProd.handle)
.imageUrl(urlSanitizer_(relatProd.featured_image))
.price(priceFactory =>
priceFactory
.original(relatProd.compare_at_price / 100)
.current(relatProd.price / 100))
)
)
)
)
)
);
});
});
});

// --------------------------------- Cart Integrations --------------------------------------
oneToOneEmbed.on("should-add-item-to-cart", (addedItem, callback) => {
shopify
.addToCart(addedItem.sku)
.then((res) => {
if (res.items) {
callback(true);
console.log("Item added succussfully!");
} else if (res.description && res.description.includes("sold out")) {
callback({
success: false,
reason: "out-of-stock"
});
} else callback(false);
})
.catch((error) => {
callback(false);
console.error("Add to cart error! ", error);
});
});

oneToOneEmbed.on("should-update-item-in-cart", (updatedItem, callback) => {
shopify
.updateItemInCart(updatedItem.sku, updatedItem.quantity)
.then((res) => {
if (res.items) {
callback(true);
console.log("Item updated succussfully!");
} else callback(false);
})
.catch((error) => {
callback(false);
console.error("Error on updating item! ", error);
});
});

oneToOneEmbed.on("goto-checkout", () => {
window.open(window.location.origin + '/cart', '_blank');
});

// --------------------------------- Provide Search Data -------------------------------------
oneToOneEmbed.on("provide-search-data", (searchRequest, searchResponse) => {
const {
term,
page
} = searchRequest;
shopify.predictiveSearch(term).then((data) => {
if (data.status && data.status === 422) throw new Error(data.message);
searchResponse((response) => {
return response
.products((p) => {
const results = data.resources.results.products.map((prod) => {
return p()
.name(prod.title)
.imageUrl(prod.image)
.sku(prod.id + ":" + prod.handle)
.price((price) =>
price
.current(parseInt(prod.price))
.original(parseInt(prod.compare_at_price_max))
);
});
return results;
})
.currency(currency())
.locale("en-US");
});
});
});
}
</script>
<script id="bambuser-one-to-one" async src="https://one-to-one.bambuser.com/embed.js"></script>


Further customization

To learn more about different ways to initiate the Call Widget or different embedding options, read the Initial Setup guide.

In the next section, we explain how you should add the integration code into your Shopify store.

Adding integration code to your Shopify​

There are different ways to add the integration code to the Shopify store. You need to make sure that your integration code is included in all pages that you want have the Call Widget embedded on.

Steps:
  1. In your Shopify theme codes, create a snippet and add the sample integration code there.
  2. Assuming that you named the snippet bambuser-call-widget.liquid, add this snippet to the theme.liquid layout as below:
theme.liquid
  {% render 'bambuser-call-widget'%}

The result​

Once you added the code snippet to your page/s, you should be seeing the following behaviour:

  1. Overlay Widget triggers on page load and can initialize Call Widget
  2. Call Widget appears in the middle of the screen
  3. You can call to the queue
  4. Agent can pickup the call from Bambuser Agent Tool (Desktop/App)
  5. Agent can search for products in the call
  6. Agent can pull the products to the call
  7. Agent can compare the products
  8. Agent can add product to the caller's cart
  9. Both Agent and Caller can modify cart quantity in the call
  10. Caller can go to checkout, which will open in the new tab

img


Implement Conversion Tracking​

To set up the Bambuser Conversion Tracking in your store, on your Shopify Admin dashboard, append the code snippet below in Settings > Checkout > Order Processing > Additional Scripts.

Global Servers

Tracking library URL is different depending on location of servers that your dashboard is set up on. Use Global servers URL https://cdn.liveshopping.bambuser.com/metrics/bambuser.min.js if the login to your Bambuser dashboard is on following link: https://lcx.bambuser.com/.

<!-- START - BAMBUSER CONVERSION TRACKING -->
<script>
function onPurchase() {
var data = {
event: "purchase", // value needs to be β€œpurchase”
orderId: Shopify.checkout.order_id.toString(), // the order id
orderValue: Shopify.checkout.total_price, // total of all products in the order
orderProductIds: Shopify.checkout.line_items.map(n => n.product_id), // comma-separated string of all product ids in the order
currency: Shopify.checkout.currency, // the currency used for the order
};

// Send the conversion data above to Bambuser
window._bambuser.collect(data);
}
// Load the tracking library and sec invoke onPurchase() method
(function(){
var bamSrcElm = document.createElement('script');
bamSrcElm.src = 'https://cdn.liveshopping.bambuser.com/metrics/bambuser.min.js';
bamSrcElm.onload = onPurchase;
document.head.appendChild(bamSrcElm);
})();
</script>
<!-- END - BAMBUSER CONVERSION TRACKING -->

To find out more, see the Bambuser Conversion Tracking πŸ›’ documentation.
If you have any issues, check out the Conversion Tracking troubleshooting checklist.