Magento Cart Integration
If you are embedding your short videos on Magento, you can enable cart integration for a seamless viewing and buying experience. Your audience will be able to accomplish these within the short video or playlist:
View product details
Select the product variation
Add to cart
Get redirected straight from cart to your check-out page
There are three components to accomplishing this:
- Creating LORA Products with SKU
- Creating your short video
- Embedding your short video and playlists into Magento
Creating LORA Products with SKU
For cart integration within short videos to work, you will need to copy the unique product identifier from your Magento Admin dashboard to your LORA CMS Product details.
- From your Magento Admin dashboard, go in the details page of your products. Copy the product SKU string you want to sync with short videos's products
- Create the product in your LORA CMS. Input the name, image and price accordingly. In the Stock Keeping Unit (SKU) field, paste the SKU value which you copied in Step 1. In the Product Link field, paste the Product URL that will redirect users to your product on your Magento website.
- Save your product and repeat for all products which you would like to add for a cart integrated short video.
Creating your short video
Now that your products have SKU fields that match your Magento admin product details, you can now create your cart integrated short videos and playlists.
Create your short video in the LORA CMS. Upload your video, input your video title, and select the Shop CTA overlay.
Select your desired Shop CTA and desired text colour.
Toggle on Cart Integration then choose Magento option
Select the products you would like to add in.
noteProducts with no SKU input can still be added into the short video. Products with no SKU will direct viewers to the product URL instead of opening the product details screen within the short video.
You can select your desired playlist to add the short video into as well. A playlist can be made up of a mix of cart integrated and non-cart integrated short videos.
notePlaylists and short videos embedded within the same Magento site will share the same cart.
After you followed the steps above, the result of your short video should look like this:
Embedding your short video and playlists with products into Magento
Now that your short videos and playlists are ready, you can easily embed them into your Magento pages.
Find out where to find your short video embed codes in this article.
After you following the steps in the article above, you will see your short video or playlist embedded into your Magento page. But to sync the player cart with your Magento site cart, you will need to add the following code to your Magento page:
Place the following custom script file in
app/code/<Vendor>/Theme/view/frontend/web/js/
directory:dangerFor demo purposes, we will use the
repositories.js
file to handle the cart integration. This file will be used to handle the cart operations such as getting the cart, adding to cart, updating cart, deleting from cart, and getting product details. In order to make it works we have to open the API endpoints for anonymous users. This is NOT recommended for production. You should implement a custom module to handle the cart operations and use the customer token to authenticate the requests.repositories.jsdefine(["jquery", "mage/url"], function ($, urlBuilder) {
"use strict";
return {
getCart: function (fetchDetails = true) {
return $.get(urlBuilder.build("rest/V1/carts/mine")).then((data) =>
!fetchDetails
? data
: Promise.all(
data.items.map((item) =>
this.getProductDetail(item.sku, {
fetchVariants: false,
})
)
).then((productsData) => ({
cartId: data.id,
currency: data.currency.store_currency_code,
items: data.items.map((item) => {
var product = productsData.find((p) => p.productSku === item.sku);
if (!product) return item;
return {
id: item.sku,
quantity: item.qty,
price: item.price,
productSku: item.sku,
name:
product.name.split("-").length > 2
? product.name.replace(/-/, " - ").replace(/-(?=[^-]*$)/, " / ")
: product.name.replace(/-/, " - "),
image: product.image,
description: product.description,
};
}),
}))
);
},
getCartItem: function (sku) {
return this.getCart(false).then((cart) => {
var item = cart.items.find((item) => item.sku === sku);
if (!item) throw new Error("Cart item not found");
return item;
});
},
addToCart: function ({ parentSku, sku, quantity, quoteId }) {
var cartItem = {
sku,
qty: quantity,
quote_id: quoteId.toString(),
};
if (parentSku && parentSku !== sku) {
return Promise.all([
this.getProductDetail(parentSku, {
fetchVariants: false,
mapLoraProduct: false,
}),
this.getProductDetail(sku, {
fetchVariants: false,
mapLoraProduct: false,
}),
]).then(([parentProductData, productData]) => {
var configurable_item_options = [];
var productOptions = parentProductData.product.extension_attributes.configurable_product_options;
productOptions.forEach((option) => {
var optionValue = this.getAttributeValue(productData.product.custom_attributes, option.label.toLowerCase());
if (optionValue) {
configurable_item_options.push({
option_id: option.attribute_id,
option_value: +optionValue,
});
}
});
cartItem.sku = parentSku;
cartItem.product_type = "configurable";
cartItem.product_option = {
extension_attributes: {
configurable_item_options,
},
};
return $.ajax({
url: urlBuilder.build("rest/V1/carts/mine/items"),
method: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({ cartItem }),
});
});
}
return $.ajax({
url: urlBuilder.build("rest/V1/carts/mine/items"),
method: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({ cartItem }),
});
},
updateCart: function ({ sku, quantity, quoteId }) {
return this.getCartItem(sku).then((item) =>
$.ajax({
url: urlBuilder.build("rest/V1/carts/mine/items/" + item.item_id),
method: "PUT",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
cartItem: {
qty: quantity,
quote_id: quoteId.toString(),
},
}),
})
);
},
deleteFromCart: function (sku) {
return this.getCartItem(sku).then((item) =>
$.ajax({
url: urlBuilder.build("rest/V1/carts/mine/items/" + item.item_id),
method: "DELETE",
})
);
},
getProductDetail: function (sku, overrideOptions = {}) {
var options = {
mapLoraProduct: true,
fetchVariants: true,
...overrideOptions,
};
return new Promise((resolve) => {
$.ajax({
url: urlBuilder.build("rest/V1/products/" + sku),
method: "GET",
}).done((product) => {
var resolvedProduct = options.mapLoraProduct ? this.mapDtoToLoraProduct({ product }) : { product };
if (!options.fetchVariants) {
resolve(resolvedProduct);
return;
}
if (product.type_id === "configurable") {
this.getProductVariants(sku)
.done((variants) => {
resolve(
options.mapLoraProduct
? this.mapDtoToLoraProduct({
product,
variants,
})
: {
product,
variants,
}
);
})
.fail(() => {
resolve(resolvedProduct);
});
} else {
resolve(resolvedProduct);
}
});
});
},
getProductVariants: function (sku) {
return $.ajax({
url: urlBuilder.build(`rest/V1/configurable-products/${sku}/children`),
method: "GET",
});
},
getAttributeValue: function (attributes, code, defaultValue = null) {
var attr = attributes.find((item) => item.attribute_code === code);
return attr ? attr.value : defaultValue;
},
mapDtoToLoraProduct: function (item) {
var variants = item.variants ? item.variants : [item.product];
var productDetail = item.product;
var configurableProductOptions = productDetail.extension_attributes.configurable_product_options || [];
var productOptions = configurableProductOptions.map((option) => {
var values = variants
.filter((variation) =>
option.values.some((v) => v.value_index === +this.getAttributeValue(variation.custom_attributes, option.label.toLowerCase()))
)
.map((variation) => {
var variantNameSplitted = variation.name.split("-");
return variantNameSplitted[option.position + 1];
});
return {
id: option.id,
name: option.label,
values: [...new Set(values)],
};
});
var imageBaseUrl = urlBuilder.build("pub/media/catalog/product");
return {
productSku: productDetail.sku,
name: productDetail.name,
image: imageBaseUrl + this.getAttributeValue(productDetail.custom_attributes, "image"),
description: this.getAttributeValue(productDetail.custom_attributes, "short_description"),
options: productOptions,
variants: variants.map((variation) => {
return {
id: variation.sku,
name:
variation.name.split("-").length > 2
? variation.name.replace(/-/, " - ").replace(/-(?=[^-]*$)/, " / ")
: variation.name.replace(/-/, " - "),
promotionPrice: +this.getAttributeValue(variation.custom_attributes, "special_price"),
price: variation.price,
options: variation.name.split("-").slice(1),
images: [
...new Set(
["image", "small_image", "thumbnail", "swatch_image"]
.map((key) => this.getAttributeValue(variation.custom_attributes, key))
.filter((image) => image !== null)
.map((path) => imageBaseUrl + path)
),
],
};
}),
};
},
};
});Update
requirejs-config.js
file inapp/code/<Vendor>/Theme/view/frontend/
directory:requirejs-config.jsvar config = {
map: {
"*": {
...
loraSdk: "https://lora-sdk.belive.sg/embed-js/testing/dev/lora-embed.js",
shortVideos: "Magento_Theme/js/short-videos",
repositories: "Magento_Theme/js/repositories",
},
},
...
};Update the
short-videos.js
in the same directory with the following code:short-videos.jsdefine(["jquery", "repositories", "loraSdk"], function ($, repositories) {
"use strict";
return function (config = {}, element) {
if (!$(element).length) return;
var defaultConfig = { component: "video-embed" };
$.extend(defaultConfig, config);
var shortVideosElement = document.createElement(config.component);
for (var key in config) {
if (key === "component") continue;
if (config.hasOwnProperty(key)) {
shortVideosElement.setAttribute(key, config[key]);
}
}
var elementId = $(element).attr("id");
if (!!elementId) {
if (config.component === "video-embed") shortVideosElement.setAttribute("video-id", elementId);
else shortVideosElement.setAttribute("playlist-id", elementId);
}
$(element).append(shortVideosElement);
shortVideosElement.eventManager.on("SYNC_PRODUCT", function (data, callback) {
console.log("SYNCED PRODUCTS:", data);
Promise.all(data.productSkus.map((sku) => repositories.getProductDetail(sku))).then((data) => {
callback(data);
});
});
shortVideosElement.eventManager.on("UPDATE_ITEM_IN_CART", (data, callback) => {
console.log("UPDATE_ITEM_IN_CART:", data);
if (data.quantity === 0) {
repositories
.deleteFromCart(data.itemId)
.then(() => callback({ success: true }))
.catch((e) =>
callback({
success: false,
message: e ? e.message : "",
})
);
return;
}
repositories
.updateCart({
sku: data.itemId,
quantity: data.quantity,
quoteId: data.cartId,
})
.then(() => callback({ success: true }))
.catch((e) =>
callback({
success: false,
message: e ? e.message : "",
})
);
});
shortVideosElement.eventManager.on("ADD_TO_CART", function (data, callback) {
console.log("ADD_TO_CART:", data);
repositories
.addToCart({
parentSku: data.properties.sku,
sku: data.itemId,
quantity: data.quantity,
quoteId: data.cartId,
})
.then(() => callback({ success: true }))
.catch((e) =>
callback({
success: false,
message: e ? e.message : "",
})
);
});
shortVideosElement.eventManager.on("SYNC_CART_STATE", function (data, callback) {
console.log("SYNC_CART_STATE:", data);
repositories.getCart().then(callback);
});
shortVideosElement.eventManager.on("CHECKOUT", function (data, callback) {
console.log("Custom CHECKOUT:", data);
window.location.href = "/checkout/cart/";
callback(true);
});
};
});
Now that you have cart integration with short videos enabled, you can now view your conversion analytics for each individual short video. To do that, go to your Short Video gallery in your LORA CMS. Click on the Action icon and click ‘Analytics’.