diff --git a/ecommerce-examples/example1/back-end/javascript/constants.js b/ecommerce-examples/example1/back-end/javascript/constants.js
new file mode 100644
index 0000000..180432e
--- /dev/null
+++ b/ecommerce-examples/example1/back-end/javascript/constants.js
@@ -0,0 +1,4 @@
+module.exports = {
+ siteName: 'SITE_ID',
+ API_KEY: 'API_KEY'
+}
\ No newline at end of file
diff --git a/ecommerce-examples/example1/back-end/javascript/fetch-estimates.js b/ecommerce-examples/example1/back-end/javascript/fetch-estimates.js
new file mode 100644
index 0000000..26efbd5
--- /dev/null
+++ b/ecommerce-examples/example1/back-end/javascript/fetch-estimates.js
@@ -0,0 +1,48 @@
+const fetch = require('node-fetch');
+const { siteName, API_KEY } = require('./constants');
+
+let credentialError = null;
+if (siteName === 'SITE_ID' || !siteName) {
+ credentialError =
+ 'Error: Kindly provide your Chargebee Site name at the Backend';
+}
+if (API_KEY === 'API_KEY' || !API_KEY) {
+ credentialError =
+ 'Error: Kindly provide your Chargebee Site API key at the Backend';
+}
+if (credentialError) {
+ throw Error(credentialError);
+}
+module.exports = async (req, res) => {
+ let data = '',
+ itemsLength = req.query?.purchase_items?.index?.length || 0;
+ for (let i = 0; i < itemsLength; i++) {
+ data = `${data}purchase_items[index][${i}]=${
+ i + 1
+ }&purchase_items[item_price_id][${i}]=${
+ req.query.purchase_items.item_price_id[i]
+ }&`;
+ if (req.query.purchase_items.quantity) {
+ data = `${data}purchase_items[quantity][${i}]=${req.query.purchase_items.quantity[i]}&`;
+ }
+ }
+ if (req.query.customer_id) {
+ data = data + 'customer_id=' + req.query.customer_id;
+ }
+ const response = await fetch(
+ `https://${siteName}.chargebee.com/api/v2/purchases/estimate?${data}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ Authorization: `Basic ${Buffer.from(API_KEY).toString('base64')}`
+ }
+ }
+ );
+ const est = await response.json();
+ if (est?.estimate?.invoice_estimates) {
+ res.status(200).json(est.estimate.invoice_estimates[0]);
+ } else {
+ res.status(400).json(est);
+ }
+};
diff --git a/ecommerce-examples/example1/back-end/javascript/fetch-items.js b/ecommerce-examples/example1/back-end/javascript/fetch-items.js
index 89a292f..691c370 100644
--- a/ecommerce-examples/example1/back-end/javascript/fetch-items.js
+++ b/ecommerce-examples/example1/back-end/javascript/fetch-items.js
@@ -12,7 +12,7 @@ const fetchItems = (req, res) => {
//handle error
console.log(error);
} else {
- var item;
+ var item = {};
if (result.list.length) {
item = result.list[0].item;
}
diff --git a/ecommerce-examples/example1/back-end/javascript/fetch-product.js b/ecommerce-examples/example1/back-end/javascript/fetch-product.js
new file mode 100644
index 0000000..81a3808
--- /dev/null
+++ b/ecommerce-examples/example1/back-end/javascript/fetch-product.js
@@ -0,0 +1,31 @@
+const fetch = require('node-fetch');
+const { siteName, API_KEY } = require('./constants');
+
+let credentialError = null;
+if (siteName === 'SITE_ID' || !siteName) {
+ credentialError =
+ 'Error: Kindly provide your Chargebee Site name at the Backend';
+}
+if (API_KEY === 'API_KEY' || !API_KEY) {
+ credentialError =
+ 'Error: Kindly provide your Chargebee Site API key at the Backend';
+}
+if (credentialError) {
+ throw Error(credentialError);
+}
+module.exports = async (req, res) => {
+ try {
+ const response = await fetch(
+ `https://${siteName}.chargebee.com/api/v2/products/${req.query.product_id}`,
+ {
+ headers: {
+ Authorization: `Basic ${Buffer.from(API_KEY).toString('base64')}`
+ }
+ }
+ );
+ const productWrapper = await response.json();
+ res.status(200).json(productWrapper.product);
+ } catch (err) {
+ console.log(err);
+ }
+};
diff --git a/ecommerce-examples/example1/back-end/javascript/fetch-variants.js b/ecommerce-examples/example1/back-end/javascript/fetch-variants.js
index 8d88760..19ffd92 100644
--- a/ecommerce-examples/example1/back-end/javascript/fetch-variants.js
+++ b/ecommerce-examples/example1/back-end/javascript/fetch-variants.js
@@ -1,6 +1,5 @@
const fetch = require('node-fetch');
-const siteName = 'SITE_ID';
-const API_KEY = 'API_KEY';
+const { siteName, API_KEY } = require('./constants');
let credentialError = null;
if (siteName === 'SITE_ID' || !siteName) {
@@ -15,19 +14,23 @@ if (credentialError) {
throw Error(credentialError);
}
module.exports = async (req, res) => {
- let variants = [];
- const response = await fetch(
- `https://${siteName}.chargebee.com/api/v2/products/${req.query.product_id}/variants`,
- {
- headers: {
- Authorization: `Basic ${Buffer.from(API_KEY).toString('base64')}`
+ try {
+ let variants = [];
+ const response = await fetch(
+ `https://${siteName}.chargebee.com/api/v2/products/${req.query.product_id}/variants`,
+ {
+ headers: {
+ Authorization: `Basic ${Buffer.from(API_KEY).toString('base64')}`
+ }
}
+ );
+ const varJson = await response.json();
+ for (let i = 0; i < varJson.list?.length; i++) {
+ const variant = varJson.list[i].variant;
+ variants.push(variant);
}
- );
- const varJson = await response.json();
- for (let i = 0; i < varJson.list.length; i++) {
- const variant = varJson.list[i].variant;
- variants.push(variant);
+ res.status(200).json({ list: variants });
+ } catch (err) {
+ console.log(err);
}
- res.status(200).json({ list: variants });
};
diff --git a/ecommerce-examples/example1/back-end/javascript/index.js b/ecommerce-examples/example1/back-end/javascript/index.js
index 7c6921c..9fd0559 100644
--- a/ecommerce-examples/example1/back-end/javascript/index.js
+++ b/ecommerce-examples/example1/back-end/javascript/index.js
@@ -4,16 +4,18 @@ const path = require('path');
const fetchItems = require('./fetch-items');
const fetchItemPrices = require('./fetch-item-prices');
const fetchVariants = require('./fetch-variants');
+const fetchProduct = require('./fetch-product');
const checkoutNew = require('./new-checkout');
+const estimates = require('./fetch-estimates');
+const { siteName, API_KEY } = require('./constants');
// CORS is enabled only for demo. Please dont use this in production unless you know about CORS
const cors = require('cors');
-const siteName = 'SITE_ID';
-const API_KEY = 'API_KEY';
let credentialError = null;
if (siteName === 'SITE_ID' || !siteName) {
- credentialError = 'Error: Kindly provide your Chargebee Site name at the Backend'
+ credentialError =
+ 'Error: Kindly provide your Chargebee Site name at the Backend';
}
if (API_KEY === 'API_KEY' || !API_KEY) {
credentialError =
@@ -28,7 +30,8 @@ chargebee.configure({
});
const app = express();
-app.use(express.urlencoded());
+app.use(express.json());
+app.use(express.urlencoded({extended: true}));
app.use(cors());
// Configure your static file paths here. Images, CSS and JS files should be inside this path
@@ -54,16 +57,37 @@ app.get('/api/variants', async (req, res) => {
await fetchVariants(req, res);
});
+/*
+ Fetch Product API
+ request params - Product ID
+*/
+app.get('/api/product', async (req, res) => {
+ await fetchProduct(req, res);
+});
+
/*
Fetch Checkout Link
request params - Item Price ID, Customer ID (optional)
*/
app.post('/api/generate_checkout_new_url', checkoutNew);
+/*
+ Fetch Estimates for the Cart items
+*/
+app.post('/api/calculate_estimates', async (req, res) => {
+ await estimates(req, res);
+});
+
// Configure the path of your HTML file to be loaded
app.get('/', (req, res) => {
res.sendFile(
- path.join(__dirname, '../../front-end/javascript/cb-widget.html')
+ path.join(__dirname, '../../front-end/javascript/widget/cb-widget.html')
+ );
+});
+
+app.get('/cart', (req, res) => {
+ res.sendFile(
+ path.join(__dirname, '../../front-end/javascript/cart/cb-cart.html')
);
});
diff --git a/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.css b/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.css
new file mode 100644
index 0000000..5c6ff33
--- /dev/null
+++ b/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.css
@@ -0,0 +1,169 @@
+.cb-cart-container {
+ width: 25%;
+ height: 100vh;
+ position: fixed;
+ top: 0;
+ right: 0;
+ background: white;
+ box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
+ z-index: 1;
+ transition: all 0.3s ease;
+ transform: translateX(100%);
+}
+
+.cb-cart-container.open {
+ transform: translateX(0%);
+}
+
+.cb-cart-container.open .cb-cart-icon {
+ display: none;
+}
+
+.cb-cart-container.close .cb-close-icon {
+ display: none;
+}
+
+.cb-cart-button {
+ position: absolute;
+ transform: translateX(-100%);
+ padding: 10px 10px 10px 15px;
+ background: white;
+ border-radius: 20px 0px 0px 20px;
+ top: 20px;
+ box-shadow: -10px 6px 13px #f4f4f4;
+ cursor: pointer;
+}
+
+.cb-cart-quantity {
+ position: absolute;
+ top: -12px;
+ left: -12px;
+ background: #022938;
+ padding: 5px 10px;
+ border-radius: 50%;
+ color: white;
+ font-family: var(--cb-font-family);
+}
+
+.cb-cart-empty {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ flex-direction: column;
+}
+
+.cb-clear-cart {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 30px;
+}
+
+.cb-clear-cart .cb-cta-secondary {
+ margin-right: 20px;
+}
+
+.cb-cart-items-wrapper {
+ padding: 0 30px;
+ overflow: auto;
+ max-height: 70vh;
+}
+
+.cb-cart-item-remove {
+ font-size: 12px;
+ cursor: pointer;
+ color: #c81f1f;
+ font-weight: 600;
+ display: inline-block;
+}
+
+.cb-delivery-info {
+ font-size: 12px;
+}
+
+.cb-cart-total-price {
+ padding: 30px 50px;
+ display: flex;
+ justify-content: space-between;
+ font-size: 24px;
+ font-weight: 600;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 20vw;
+ background: white;
+ box-shadow: rgba(0, 0, 0, 0.35) 15px 5px 15px;
+}
+
+.cb-cart-total-price div:first-child {
+ margin-right: 50px;
+}
+
+.cb-customer-info {
+ margin: auto;
+ width: 70%;
+}
+
+.cb-customer-info input,
+.cb-customer-info select {
+ height: 32px;
+ width: 100%;
+ margin: 10px 0;
+ padding: 0 10px;
+ border-radius: 5px;
+ border: 1px solid #9797a5;
+}
+
+.cb-item-image img {
+ width: 125px;
+ border-radius: 5px;
+}
+
+.cb-cart-item-container {
+ display: flex;
+ padding: 15px 0;
+ border-top: 1px solid #9797a5;
+ align-items: center;
+}
+
+.cb-item-detail {
+ padding: 0 15px;
+}
+
+.cb-productInfo,
+.cb-unitPrice,
+.cb-cart-item-quantity {
+ margin-bottom: 10px;
+}
+
+.cb-cart-item-quantity {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.cb-cart-item-container .cb-quantity-wrapper {
+ border: none;
+}
+
+.cb-cart-item-container .cb-quantity-wrapper button {
+ border-radius: 50%;
+ border: 1px solid #d7d5dd;
+ padding: 5px 10px;
+ background: #d7d5dd;
+ cursor: pointer;
+}
+
+.cb-continue-shopping {
+ padding: 10px 20px;
+ background-color: var(--cb-cta-bg);
+ color: var(--cb-cta-text);
+ border-radius: var(--cb-border-radius);
+ text-decoration: none;
+ display: block;
+ text-align: center;
+ border: none;
+ width: 80%;
+ margin: 30px auto;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.html b/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.html
new file mode 100644
index 0000000..1feed16
--- /dev/null
+++ b/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.html
@@ -0,0 +1,62 @@
+
+
+
+
+ Your cart is empty!
+
+
+
+
+
+
+
+
+
diff --git a/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.js b/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.js
new file mode 100644
index 0000000..772d5fa
--- /dev/null
+++ b/ecommerce-examples/example1/front-end/javascript/cart/cb-cart.js
@@ -0,0 +1,258 @@
+var cbInstance = Chargebee.init({
+ site: 'pc-pim-test', // your test site
+ isItemsModel: true
+});
+
+const CbCart = {
+ inited: false,
+ options: {},
+ estimates: null,
+ init: async function (options = {}) {
+ this.options = {
+ ...this.options,
+ ...options
+ };
+ await this.embedCart();
+ document
+ .querySelector('.cb-cart-button')
+ .addEventListener('click', this.toggleCart);
+ this.initializeLocalStorage();
+ },
+ embedCart: async function () {
+ if (this.inited) return;
+ const fetchTemplate = await fetch('/cart');
+ const cart = await fetchTemplate.text();
+ document.querySelector('.cb-cart-container').innerHTML = cart;
+ this.inited = true;
+ },
+ initializeLocalStorage: function () {
+ let localCart = localStorage.getItem('CbCart');
+ if (localCart) {
+ const { lastUpdated } = JSON.parse(localCart);
+ const timeDiff = (Date.now() - lastUpdated) / (1000 * 60 * 60 * 24);
+ if (parseInt(timeDiff) > 30) {
+ localCart = null;
+ }
+ }
+ if (!localStorage.getItem('CbCart')) {
+ localStorage.setItem(
+ 'CbCart',
+ JSON.stringify({
+ ...(localCart || {
+ ...cbInstance.getCart()
+ }),
+ lastUpdated: Date.now()
+ })
+ );
+ }
+ const cartProducts = JSON.parse(localCart)?.products || [];
+ const cartInstance = cbInstance.getCart();
+ cartInstance.products = cartProducts;
+ for (let i = 0; i < cartProducts.length; i++) {
+ this.renderCartItem(cartProducts[i]);
+ }
+ },
+ updateLocalStorage: function () {
+ localStorage.setItem(
+ 'CbCart',
+ JSON.stringify({
+ ...cbInstance.getCart(),
+ lastUpdated: Date.now()
+ })
+ );
+ },
+ addProductToCart: function (productOptions) {
+ const cart = cbInstance.getCart();
+ const existingProduct = this.findProduct(productOptions.itemPriceId);
+ if (existingProduct) {
+ if (existingProduct.planQuantity + productOptions.quantity < 1) return;
+ existingProduct.planQuantity += productOptions.quantity;
+ existingProduct.data = {
+ ...existingProduct.data,
+ quantity: existingProduct.planQuantity
+ };
+ this.updateCartRow(existingProduct);
+ return;
+ }
+ const product = cbInstance.initializeProduct(
+ productOptions.itemId,
+ productOptions.quantity,
+ true
+ );
+ product.addItem(
+ product.createItem(
+ productOptions.itemPriceId,
+ productOptions.quantity,
+ productOptions.type
+ )
+ );
+ this.addCustomData(product, productOptions);
+ cart.products.push(product);
+ this.renderCartItem(product);
+ },
+ addCustomData: function (product, productParams) {
+ product.data = {
+ productName: productParams.productInfo.name,
+ image: productParams.productInfo.image,
+ variantName: productParams.productInfo.variantName,
+ deliveryInfo: productParams.productInfo.deliveryInfo,
+ quantity: product.planQuantity,
+ unitPrice: productParams.productInfo.price,
+ currencyCode: this.options.currency
+ };
+ },
+ renderCartItem: function (product) {
+ const template = (data) => {
+ return `
+
+
+
+
+
+ ${data.productName} - ${data.variantName}
+
+ ${data.deliveryInfo}
+
+
+
+ ${data.unitPrice} ${data.currency}
+
+
+
+
+
+ ${data.quantity}
+
+
+
+
+ Remove
+
+
+ `;
+ };
+ const wrapper = document.querySelector('.cb-cart-items-wrapper');
+ const row = document.createElement('div');
+ row.id = `price-${product.items[0].item_price_id}`;
+ row.className = 'cb-cart-item-container';
+ row.innerHTML = template({
+ id: product.items[0].item_price_id,
+ ...product.data,
+ currency: this.options.currency
+ });
+ wrapper.appendChild(row);
+ this.updateCartQuantity();
+ this.calculateEstimate();
+ if (!document.querySelector('.cb-cart-empty.cb-hide')) {
+ document.querySelector('.cb-cart-empty').className =
+ 'cb-cart-empty cb-hide';
+ document.querySelector('.cb-cart-hasitem').className = 'cb-cart-hasitem';
+ }
+ },
+ updateCartQuantity: function () {
+ const cart = cbInstance.getCart();
+ const countContainer = document.querySelector('.cb-cart-quantity');
+ countContainer.innerText = cart.products.length;
+ },
+ updateCartRow: function (product) {
+ const priceId = product.items[0].item_price_id;
+ const row = document.querySelector(`#price-${priceId}`);
+ row.querySelector('.cb-cart-item-quantity-input').innerText =
+ product.planQuantity;
+ this.calculateEstimate();
+ },
+ deleteCartItem: function (itemPriceId) {
+ const cart = cbInstance.getCart();
+ const productIndex = cart.products.findIndex((item) => {
+ return item.items[0].item_price_id === itemPriceId;
+ });
+ document.querySelector(`#price-${itemPriceId}`)?.remove();
+ cart.products.splice(productIndex, 1);
+ this.updateCartQuantity();
+ if (!cart.products.length) {
+ this.clearCart();
+ } else {
+ this.calculateEstimate();
+ }
+ },
+ calculateEstimate: async function () {
+ let query = '';
+ const cart = cbInstance.getCart();
+ cart.products.forEach((product, index) => {
+ query = `${query}purchase_items[index][${index}]=${
+ index + 1
+ }&purchase_items[item_price_id][${index}]=${
+ product.items[0].item_price_id
+ }&`;
+ query = `${query}purchase_items[quantity][${index}]=${product.planQuantity}&`;
+ });
+ document.querySelector('#cb-cart-total-display').innerText =
+ 'calculating...';
+ try {
+ const res = await fetch(`/api/calculate_estimates?${query}`, {
+ method: 'POST',
+ headers: {
+ 'Content-type': 'application/json; charset=UTF-8'
+ }
+ });
+ const estimates = await res.json();
+ this.estimates = estimates;
+ } catch (err) {
+ console.error(err);
+ }
+ this.updateCartPrice();
+ this.updateLocalStorage();
+ },
+ updateCartPrice: function () {
+ if (this.estimates.total) {
+ document.querySelector('#cb-cart-total-display').innerText = `${(
+ this.estimates.total / 100
+ ).toFixed(2)} ${this.options.currency}`;
+ }
+ },
+ findProduct: function (itemPriceId) {
+ const cart = cbInstance.getCart();
+ const productIndex = cart.products.findIndex((item) => {
+ return item.items[0].item_price_id === itemPriceId;
+ });
+ return productIndex !== -1 ? cart.products[productIndex] : null;
+ },
+ modifyQuantity: function (itemPriceId, qty) {
+ this.addProductToCart({
+ itemPriceId,
+ quantity: qty
+ });
+ },
+ clearCart: function () {
+ const cart = cbInstance.getCart();
+ cart.products = [];
+ document.querySelector('.cb-cart-empty').className = 'cb-cart-empty';
+ document.querySelector('.cb-cart-hasitem').className =
+ 'cb-cart-hasitem cb-hide';
+ document.querySelector('.cb-cart-items-wrapper').innerHTML = '';
+ this.updateCartQuantity();
+ this.updateLocalStorage();
+ },
+ toggleCart: function () {
+ if (document.querySelector('.cb-cart-container.open')) {
+ document.querySelector('.cb-cart-container').className =
+ 'cb-cart-container close';
+ } else {
+ document.querySelector('.cb-cart-container').className =
+ 'cb-cart-container open';
+ }
+ }
+};
+
+CbCart.init({
+ currency: 'USD'
+});
diff --git a/ecommerce-examples/example1/front-end/javascript/cb-widget.html b/ecommerce-examples/example1/front-end/javascript/cb-widget.html
deleted file mode 100644
index ef4f1ce..0000000
--- a/ecommerce-examples/example1/front-end/javascript/cb-widget.html
+++ /dev/null
@@ -1,185 +0,0 @@
-
-
-
- PDP Widget
-
-
-
-
-
-
-
-
-
diff --git a/ecommerce-examples/example1/front-end/javascript/constants/countries.js b/ecommerce-examples/example1/front-end/javascript/constants/countries.js
new file mode 100644
index 0000000..4fc7e44
--- /dev/null
+++ b/ecommerce-examples/example1/front-end/javascript/constants/countries.js
@@ -0,0 +1,251 @@
+const CbCountries = [
+ { Code: 'AF', Name: 'Afghanistan' },
+ { Code: 'AX', Name: '\u00c5land Islands' },
+ { Code: 'AL', Name: 'Albania' },
+ { Code: 'DZ', Name: 'Algeria' },
+ { Code: 'AS', Name: 'American Samoa' },
+ { Code: 'AD', Name: 'Andorra' },
+ { Code: 'AO', Name: 'Angola' },
+ { Code: 'AI', Name: 'Anguilla' },
+ { Code: 'AQ', Name: 'Antarctica' },
+ { Code: 'AG', Name: 'Antigua and Barbuda' },
+ { Code: 'AR', Name: 'Argentina' },
+ { Code: 'AM', Name: 'Armenia' },
+ { Code: 'AW', Name: 'Aruba' },
+ { Code: 'AU', Name: 'Australia' },
+ { Code: 'AT', Name: 'Austria' },
+ { Code: 'AZ', Name: 'Azerbaijan' },
+ { Code: 'BS', Name: 'Bahamas' },
+ { Code: 'BH', Name: 'Bahrain' },
+ { Code: 'BD', Name: 'Bangladesh' },
+ { Code: 'BB', Name: 'Barbados' },
+ { Code: 'BY', Name: 'Belarus' },
+ { Code: 'BE', Name: 'Belgium' },
+ { Code: 'BZ', Name: 'Belize' },
+ { Code: 'BJ', Name: 'Benin' },
+ { Code: 'BM', Name: 'Bermuda' },
+ { Code: 'BT', Name: 'Bhutan' },
+ { Code: 'BO', Name: 'Bolivia, Plurinational State of' },
+ { Code: 'BQ', Name: 'Bonaire, Sint Eustatius and Saba' },
+ { Code: 'BA', Name: 'Bosnia and Herzegovina' },
+ { Code: 'BW', Name: 'Botswana' },
+ { Code: 'BV', Name: 'Bouvet Island' },
+ { Code: 'BR', Name: 'Brazil' },
+ { Code: 'IO', Name: 'British Indian Ocean Territory' },
+ { Code: 'BN', Name: 'Brunei Darussalam' },
+ { Code: 'BG', Name: 'Bulgaria' },
+ { Code: 'BF', Name: 'Burkina Faso' },
+ { Code: 'BI', Name: 'Burundi' },
+ { Code: 'KH', Name: 'Cambodia' },
+ { Code: 'CM', Name: 'Cameroon' },
+ { Code: 'CA', Name: 'Canada' },
+ { Code: 'CV', Name: 'Cape Verde' },
+ { Code: 'KY', Name: 'Cayman Islands' },
+ { Code: 'CF', Name: 'Central African Republic' },
+ { Code: 'TD', Name: 'Chad' },
+ { Code: 'CL', Name: 'Chile' },
+ { Code: 'CN', Name: 'China' },
+ { Code: 'CX', Name: 'Christmas Island' },
+ { Code: 'CC', Name: 'Cocos (Keeling) Islands' },
+ { Code: 'CO', Name: 'Colombia' },
+ { Code: 'KM', Name: 'Comoros' },
+ { Code: 'CG', Name: 'Congo' },
+ { Code: 'CD', Name: 'Congo, the Democratic Republic of the' },
+ { Code: 'CK', Name: 'Cook Islands' },
+ { Code: 'CR', Name: 'Costa Rica' },
+ { Code: 'CI', Name: "C\u00f4te d'Ivoire" },
+ { Code: 'HR', Name: 'Croatia' },
+ { Code: 'CU', Name: 'Cuba' },
+ { Code: 'CW', Name: 'Cura\u00e7ao' },
+ { Code: 'CY', Name: 'Cyprus' },
+ { Code: 'CZ', Name: 'Czech Republic' },
+ { Code: 'DK', Name: 'Denmark' },
+ { Code: 'DJ', Name: 'Djibouti' },
+ { Code: 'DM', Name: 'Dominica' },
+ { Code: 'DO', Name: 'Dominican Republic' },
+ { Code: 'EC', Name: 'Ecuador' },
+ { Code: 'EG', Name: 'Egypt' },
+ { Code: 'SV', Name: 'El Salvador' },
+ { Code: 'GQ', Name: 'Equatorial Guinea' },
+ { Code: 'ER', Name: 'Eritrea' },
+ { Code: 'EE', Name: 'Estonia' },
+ { Code: 'ET', Name: 'Ethiopia' },
+ { Code: 'FK', Name: 'Falkland Islands (Malvinas)' },
+ { Code: 'FO', Name: 'Faroe Islands' },
+ { Code: 'FJ', Name: 'Fiji' },
+ { Code: 'FI', Name: 'Finland' },
+ { Code: 'FR', Name: 'France' },
+ { Code: 'GF', Name: 'French Guiana' },
+ { Code: 'PF', Name: 'French Polynesia' },
+ { Code: 'TF', Name: 'French Southern Territories' },
+ { Code: 'GA', Name: 'Gabon' },
+ { Code: 'GM', Name: 'Gambia' },
+ { Code: 'GE', Name: 'Georgia' },
+ { Code: 'DE', Name: 'Germany' },
+ { Code: 'GH', Name: 'Ghana' },
+ { Code: 'GI', Name: 'Gibraltar' },
+ { Code: 'GR', Name: 'Greece' },
+ { Code: 'GL', Name: 'Greenland' },
+ { Code: 'GD', Name: 'Grenada' },
+ { Code: 'GP', Name: 'Guadeloupe' },
+ { Code: 'GU', Name: 'Guam' },
+ { Code: 'GT', Name: 'Guatemala' },
+ { Code: 'GG', Name: 'Guernsey' },
+ { Code: 'GN', Name: 'Guinea' },
+ { Code: 'GW', Name: 'Guinea-Bissau' },
+ { Code: 'GY', Name: 'Guyana' },
+ { Code: 'HT', Name: 'Haiti' },
+ { Code: 'HM', Name: 'Heard Island and McDonald Islands' },
+ { Code: 'VA', Name: 'Holy See (Vatican City State)' },
+ { Code: 'HN', Name: 'Honduras' },
+ { Code: 'HK', Name: 'Hong Kong' },
+ { Code: 'HU', Name: 'Hungary' },
+ { Code: 'IS', Name: 'Iceland' },
+ { Code: 'IN', Name: 'India' },
+ { Code: 'ID', Name: 'Indonesia' },
+ { Code: 'IR', Name: 'Iran, Islamic Republic of' },
+ { Code: 'IQ', Name: 'Iraq' },
+ { Code: 'IE', Name: 'Ireland' },
+ { Code: 'IM', Name: 'Isle of Man' },
+ { Code: 'IL', Name: 'Israel' },
+ { Code: 'IT', Name: 'Italy' },
+ { Code: 'JM', Name: 'Jamaica' },
+ { Code: 'JP', Name: 'Japan' },
+ { Code: 'JE', Name: 'Jersey' },
+ { Code: 'JO', Name: 'Jordan' },
+ { Code: 'KZ', Name: 'Kazakhstan' },
+ { Code: 'KE', Name: 'Kenya' },
+ { Code: 'KI', Name: 'Kiribati' },
+ { Code: 'KP', Name: "Korea, Democratic People's Republic of" },
+ { Code: 'KR', Name: 'Korea, Republic of' },
+ { Code: 'KW', Name: 'Kuwait' },
+ { Code: 'KG', Name: 'Kyrgyzstan' },
+ { Code: 'LA', Name: "Lao People's Democratic Republic" },
+ { Code: 'LV', Name: 'Latvia' },
+ { Code: 'LB', Name: 'Lebanon' },
+ { Code: 'LS', Name: 'Lesotho' },
+ { Code: 'LR', Name: 'Liberia' },
+ { Code: 'LY', Name: 'Libya' },
+ { Code: 'LI', Name: 'Liechtenstein' },
+ { Code: 'LT', Name: 'Lithuania' },
+ { Code: 'LU', Name: 'Luxembourg' },
+ { Code: 'MO', Name: 'Macao' },
+ { Code: 'MK', Name: 'Macedonia, the Former Yugoslav Republic of' },
+ { Code: 'MG', Name: 'Madagascar' },
+ { Code: 'MW', Name: 'Malawi' },
+ { Code: 'MY', Name: 'Malaysia' },
+ { Code: 'MV', Name: 'Maldives' },
+ { Code: 'ML', Name: 'Mali' },
+ { Code: 'MT', Name: 'Malta' },
+ { Code: 'MH', Name: 'Marshall Islands' },
+ { Code: 'MQ', Name: 'Martinique' },
+ { Code: 'MR', Name: 'Mauritania' },
+ { Code: 'MU', Name: 'Mauritius' },
+ { Code: 'YT', Name: 'Mayotte' },
+ { Code: 'MX', Name: 'Mexico' },
+ { Code: 'FM', Name: 'Micronesia, Federated States of' },
+ { Code: 'MD', Name: 'Moldova, Republic of' },
+ { Code: 'MC', Name: 'Monaco' },
+ { Code: 'MN', Name: 'Mongolia' },
+ { Code: 'ME', Name: 'Montenegro' },
+ { Code: 'MS', Name: 'Montserrat' },
+ { Code: 'MA', Name: 'Morocco' },
+ { Code: 'MZ', Name: 'Mozambique' },
+ { Code: 'MM', Name: 'Myanmar' },
+ { Code: 'NA', Name: 'Namibia' },
+ { Code: 'NR', Name: 'Nauru' },
+ { Code: 'NP', Name: 'Nepal' },
+ { Code: 'NL', Name: 'Netherlands' },
+ { Code: 'NC', Name: 'New Caledonia' },
+ { Code: 'NZ', Name: 'New Zealand' },
+ { Code: 'NI', Name: 'Nicaragua' },
+ { Code: 'NE', Name: 'Niger' },
+ { Code: 'NG', Name: 'Nigeria' },
+ { Code: 'NU', Name: 'Niue' },
+ { Code: 'NF', Name: 'Norfolk Island' },
+ { Code: 'MP', Name: 'Northern Mariana Islands' },
+ { Code: 'NO', Name: 'Norway' },
+ { Code: 'OM', Name: 'Oman' },
+ { Code: 'PK', Name: 'Pakistan' },
+ { Code: 'PW', Name: 'Palau' },
+ { Code: 'PS', Name: 'Palestine, State of' },
+ { Code: 'PA', Name: 'Panama' },
+ { Code: 'PG', Name: 'Papua New Guinea' },
+ { Code: 'PY', Name: 'Paraguay' },
+ { Code: 'PE', Name: 'Peru' },
+ { Code: 'PH', Name: 'Philippines' },
+ { Code: 'PN', Name: 'Pitcairn' },
+ { Code: 'PL', Name: 'Poland' },
+ { Code: 'PT', Name: 'Portugal' },
+ { Code: 'PR', Name: 'Puerto Rico' },
+ { Code: 'QA', Name: 'Qatar' },
+ { Code: 'RE', Name: 'R\u00e9union' },
+ { Code: 'RO', Name: 'Romania' },
+ { Code: 'RU', Name: 'Russian Federation' },
+ { Code: 'RW', Name: 'Rwanda' },
+ { Code: 'BL', Name: 'Saint Barth\u00e9lemy' },
+ { Code: 'SH', Name: 'Saint Helena, Ascension and Tristan da Cunha' },
+ { Code: 'KN', Name: 'Saint Kitts and Nevis' },
+ { Code: 'LC', Name: 'Saint Lucia' },
+ { Code: 'MF', Name: 'Saint Martin (French part)' },
+ { Code: 'PM', Name: 'Saint Pierre and Miquelon' },
+ { Code: 'VC', Name: 'Saint Vincent and the Grenadines' },
+ { Code: 'WS', Name: 'Samoa' },
+ { Code: 'SM', Name: 'San Marino' },
+ { Code: 'ST', Name: 'Sao Tome and Principe' },
+ { Code: 'SA', Name: 'Saudi Arabia' },
+ { Code: 'SN', Name: 'Senegal' },
+ { Code: 'RS', Name: 'Serbia' },
+ { Code: 'SC', Name: 'Seychelles' },
+ { Code: 'SL', Name: 'Sierra Leone' },
+ { Code: 'SG', Name: 'Singapore' },
+ { Code: 'SX', Name: 'Sint Maarten (Dutch part)' },
+ { Code: 'SK', Name: 'Slovakia' },
+ { Code: 'SI', Name: 'Slovenia' },
+ { Code: 'SB', Name: 'Solomon Islands' },
+ { Code: 'SO', Name: 'Somalia' },
+ { Code: 'ZA', Name: 'South Africa' },
+ { Code: 'GS', Name: 'South Georgia and the South Sandwich Islands' },
+ { Code: 'SS', Name: 'South Sudan' },
+ { Code: 'ES', Name: 'Spain' },
+ { Code: 'LK', Name: 'Sri Lanka' },
+ { Code: 'SD', Name: 'Sudan' },
+ { Code: 'SR', Name: 'Suriname' },
+ { Code: 'SJ', Name: 'Svalbard and Jan Mayen' },
+ { Code: 'SZ', Name: 'Swaziland' },
+ { Code: 'SE', Name: 'Sweden' },
+ { Code: 'CH', Name: 'Switzerland' },
+ { Code: 'SY', Name: 'Syrian Arab Republic' },
+ { Code: 'TW', Name: 'Taiwan, Province of China' },
+ { Code: 'TJ', Name: 'Tajikistan' },
+ { Code: 'TZ', Name: 'Tanzania, United Republic of' },
+ { Code: 'TH', Name: 'Thailand' },
+ { Code: 'TL', Name: 'Timor-Leste' },
+ { Code: 'TG', Name: 'Togo' },
+ { Code: 'TK', Name: 'Tokelau' },
+ { Code: 'TO', Name: 'Tonga' },
+ { Code: 'TT', Name: 'Trinidad and Tobago' },
+ { Code: 'TN', Name: 'Tunisia' },
+ { Code: 'TR', Name: 'Turkey' },
+ { Code: 'TM', Name: 'Turkmenistan' },
+ { Code: 'TC', Name: 'Turks and Caicos Islands' },
+ { Code: 'TV', Name: 'Tuvalu' },
+ { Code: 'UG', Name: 'Uganda' },
+ { Code: 'UA', Name: 'Ukraine' },
+ { Code: 'AE', Name: 'United Arab Emirates' },
+ { Code: 'GB', Name: 'United Kingdom' },
+ { Code: 'US', Name: 'United States' },
+ { Code: 'UM', Name: 'United States Minor Outlying Islands' },
+ { Code: 'UY', Name: 'Uruguay' },
+ { Code: 'UZ', Name: 'Uzbekistan' },
+ { Code: 'VU', Name: 'Vanuatu' },
+ { Code: 'VE', Name: 'Venezuela, Bolivarian Republic of' },
+ { Code: 'VN', Name: 'Viet Nam' },
+ { Code: 'VG', Name: 'Virgin Islands, British' },
+ { Code: 'VI', Name: 'Virgin Islands, U.S.' },
+ { Code: 'WF', Name: 'Wallis and Futuna' },
+ { Code: 'EH', Name: 'Western Sahara' },
+ { Code: 'YE', Name: 'Yemen' },
+ { Code: 'ZM', Name: 'Zambia' },
+ { Code: 'ZW', Name: 'Zimbabwe' }
+];
diff --git a/ecommerce-examples/example1/front-end/javascript/images/empty-cart.png b/ecommerce-examples/example1/front-end/javascript/images/empty-cart.png
new file mode 100644
index 0000000..5664add
Binary files /dev/null and b/ecommerce-examples/example1/front-end/javascript/images/empty-cart.png differ
diff --git a/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.css b/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.css
new file mode 100644
index 0000000..ebdded0
--- /dev/null
+++ b/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.css
@@ -0,0 +1,157 @@
+/*
+ CSS Variables - Configure styling by modifying these variables
+*/
+body {
+ --cb-font-family: system-ui, -apple-system, BlinkMacSystemFont,
+ 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
+ 'Helvetica Neue', sans-serif;
+ --cb-font-size: 14px;
+ --cb-font-color: #012a38;
+ --cb-border-radius: 4px;
+ --cb-box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
+ --cb-widget-bg: #fff;
+ --cb-cta-bg: #012a38;
+ --cb-cta-text: #fff;
+ --cb-variant-bg: #fff;
+ --cb-variant-active-bg: #137cb6;
+ --cb-variant-border: 1px solid #012a38;
+}
+
+body {
+ min-height: 100vh;
+ font-family: var(--cb-font-family);
+}
+
+body .cb-hide {
+ display: none;
+}
+
+.cb-loading-container {
+ text-align: center;
+ margin-top: 180px;
+}
+
+.cb-button-disabled {
+ pointer-events: none;
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+.cb-widget-container {
+ position: fixed;
+ min-height: 400px;
+ width: 400px;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ max-width: 400px;
+ padding: 30px 20px;
+ border: 1px solid #ccc;
+ border-radius: var(--cb-border-radius);
+ font-family: var(--cb-font-family);
+ font-size: var(--cb-font-size);
+ color: var(--cb-font-color);
+ box-shadow: var(--cb-box-shadow);
+ background-color: var(--cb-widget-bg);
+}
+
+.cb-widget-title {
+ margin-top: 0;
+}
+
+.cb-variant-select-wrapper,
+.cb-frequency-select,
+.cb-frequency-select select,
+.cb-quantity {
+ margin-bottom: 10px;
+}
+
+.cb-quantity {
+ padding-top: 20px;
+}
+
+.cb-quantity label {
+ margin-right: 20px;
+}
+
+.cb-quantity button {
+ border: none;
+ padding: 5px 10px;
+ cursor: pointer;
+}
+
+.cb-quantity-wrapper {
+ display: inline-block;
+ border: 1px solid gray;
+}
+
+.cb-quantity-wrapper span {
+ margin: 0 15px;
+}
+
+.cb-frequency-select select,
+.cb-variant-select {
+ display: block;
+ margin-top: 10px;
+ width: 100%;
+ height: 36px;
+ padding: 5px;
+ border-radius: var(--cb-border-radius);
+}
+
+.cb-cta-button {
+ padding: 10px 20px;
+ background-color: var(--cb-cta-bg);
+ color: var(--cb-cta-text);
+ border-radius: var(--cb-border-radius);
+ text-decoration: none;
+ display: block;
+ text-align: center;
+ margin-top: 10px;
+}
+
+.cb-cta-secondary {
+ padding: 10px 20px;
+ background-color: #c81f1e;
+ color: var(--cb-cta-text);
+ border-radius: var(--cb-border-radius);
+ text-decoration: none;
+ display: block;
+ text-align: center;
+ margin-top: 10px;
+ border: none;
+ cursor: pointer;
+}
+
+.cb-variant-selector {
+ padding: 10px 0;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+}
+
+.cb-variant-selector button {
+ margin-top: 10px;
+ padding: 10px 16px;
+ border-radius: var(--cb-border-radius);
+ border: var(--cb-variant-border);
+ background: var(--cb-variant-bg);
+ cursor: pointer;
+}
+
+.cb-variant-selector button.active {
+ background: var(--cb-variant-active-bg);
+ color: #fff;
+ border: 0;
+}
+
+.cb-cta-container {
+ margin-top: 30px;
+}
+
+.cb-add-to-cart {
+ background-color: #e5f3fa;
+ border-color: #8ccce6;
+ color: #1d485f;
+ border: 1px solid #0229383d;
+}
\ No newline at end of file
diff --git a/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.html b/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.html
new file mode 100644
index 0000000..ea15f22
--- /dev/null
+++ b/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.html
@@ -0,0 +1,68 @@
+
+
+
+ PDP Widget
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ecommerce-examples/example1/front-end/javascript/cb-widget.js b/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.js
similarity index 76%
rename from ecommerce-examples/example1/front-end/javascript/cb-widget.js
rename to ecommerce-examples/example1/front-end/javascript/widget/cb-widget.js
index 5e3ff83..8eb06e6 100644
--- a/ecommerce-examples/example1/front-end/javascript/cb-widget.js
+++ b/ecommerce-examples/example1/front-end/javascript/widget/cb-widget.js
@@ -9,6 +9,7 @@ const CbWidget = {
showAddToCart: true, // Display Add to Cart in your widget
showSubscribeNow: true // Display Subscribe now in your widget
},
+ productInfo: {},
variants: [],
quantity: 1,
prices: {
@@ -54,14 +55,32 @@ const CbWidget = {
}
// Add event handlers for '-' and '+' buttons of Quantity selectors
document
- .querySelector('.cb-decrement-btn')
+ .querySelector('.cb-quantity .cb-decrement-btn')
.addEventListener('click', () => this.quantityModifier(-1));
document
- .querySelector('.cb-increment-btn')
+ .querySelector('.cb-quantity .cb-increment-btn')
.addEventListener('click', () => this.quantityModifier(1));
// Show blocks based on the options provided
if (!this.options.showAddToCart) {
this.toggleBlock('#cb-add-to-cart', true);
+ } else {
+ document
+ .querySelector('#cb-add-to-cart')
+ .addEventListener('click', () => {
+ CbCart.addProductToCart({
+ itemId: this.widgetData.selectedFrequency.itemId,
+ itemPriceId: this.widgetData.selectedFrequency.id,
+ type: this.widgetData.selectedFrequency.type,
+ quantity: this.quantity,
+ productInfo: {
+ ...this.productInfo,
+ variantName: this.widgetData.selectedFrequency.variantName,
+ deliveryInfo: document.querySelector('.cb-delivery-interval')
+ .innerText,
+ price: this.widgetData.selectedFrequency.price
+ }
+ });
+ });
}
if (!this.options.showSubscribeNow) {
this.toggleBlock('#cb-checkout', true);
@@ -75,22 +94,30 @@ const CbWidget = {
retrieveData: async function () {
let error = null;
// Fetch Variant, Plans, Charges info
- const [variants, subscriptionPlans, oneTimeCharges] = await Promise.all([
- this.fetchCBApi('/api/variants?product_id=' + this.options.product_id),
- this.fetchCBApi(
- '/api/fetch-items?product_id=' + this.options.product_id + '&type=plan'
- ),
- this.fetchCBApi(
- '/api/fetch-items?product_id=' +
- this.options.product_id +
- '&type=charge'
- )
- ]).catch((err) => {
- // Show Error message on the widget
- this.showError();
- console.error(err);
- error = err;
- });
+ const [product, variants, subscriptionPlans, oneTimeCharges] =
+ await Promise.all([
+ this.fetchCBApi('/api/product?product_id=' + this.options.product_id),
+ this.fetchCBApi('/api/variants?product_id=' + this.options.product_id),
+ this.fetchCBApi(
+ '/api/fetch-items?product_id=' +
+ this.options.product_id +
+ '&type=plan'
+ ),
+ this.fetchCBApi(
+ '/api/fetch-items?product_id=' +
+ this.options.product_id +
+ '&type=charge'
+ )
+ ]).catch((err) => {
+ // Show Error message on the widget
+ this.showError();
+ console.error(err);
+ error = err;
+ });
+ this.productInfo = {
+ name: product.name,
+ image: product?.metadata?.image
+ };
// Fetch Subscription price and one time prices
const [subscriptionPrices, oneTimePrices] = await Promise.all([
this.fetchCBApi('/api/fetch-item-prices?item_id=' + subscriptionPlans.id),
@@ -201,6 +228,9 @@ const CbWidget = {
frequencies.forEach((frequency) => {
const option = document.createElement('option');
option.value = frequency.id;
+ option.dataset.itemId = frequency.item_id;
+ option.dataset.variant = this.widgetData[variantId].name;
+ option.dataset.price = (frequency.price / 100).toFixed(2);
// Construct frequency text
let frequencyText = '';
if (frequency.period === 1) {
@@ -221,7 +251,9 @@ const CbWidget = {
frequency.shipping_period_unit
}${frequency.shipping_period === 1 ? '' : 's'}`;
}
- option.dataset.description = `${shippingText}
${frequency.description || ''}`;
+ option.dataset.description = `${shippingText}
${
+ frequency.description || ''
+ }`;
frequencySelector.appendChild(option);
});
frequencySelector.addEventListener('change', (e) => {
@@ -232,9 +264,18 @@ const CbWidget = {
changeFrequency: function (e) {
this.widgetData.selectedFrequency = {
id: e.target.value,
- type: e.target.value?.endsWith(`-Charge-${this.options.currency}`)
+ itemId: document.querySelector(
+ '#cb-frequency [value="' + e.target.value + '"]'
+ )?.dataset?.itemId,
+ type: e.target.value?.endsWith(`-charge-${this.options.currency}`)
? 'charge'
- : 'plan'
+ : 'plan',
+ variantName: document.querySelector(
+ '#cb-frequency [value="' + e.target.value + '"]'
+ )?.dataset?.variant,
+ price: document.querySelector(
+ '#cb-frequency [value="' + e.target.value + '"]'
+ )?.dataset?.price
};
const subsDescription = document.querySelector('.cb-subs-description');
subsDescription.innerHTML = '';
@@ -245,13 +286,14 @@ const CbWidget = {
},
subscribeNow: async function (e) {
e.preventDefault();
+ let url = `/api/generate_checkout_new_url?subscription_items[item_price_id][0]=${this.widgetData.selectedFrequency.id}&customer[id]=${this.options.customer_id}¤cy_code=${this.options.currency}&item_type=${this.widgetData.selectedFrequency.type}`;
+ if (this.widgetData.selectedFrequency.type !== 'flat_fee') {
+ url = `${url}&subscription_items[quantity][0]=${this.quantity}`;
+ }
try {
- const checkout = await this.fetchCBApi(
- `/api/generate_checkout_new_url?subscription_items[item_price_id][0]=${this.widgetData.selectedFrequency.id}&subscription_items[quantity][0]=${this.quantity}&customer[id]=${this.options.customer_id}¤cy_code=${this.options.currency}&item_type=${this.widgetData.selectedFrequency.type}`,
- {
- method: 'POST'
- }
- );
+ const checkout = await this.fetchCBApi(url, {
+ method: 'POST'
+ });
window.location.href = checkout.url;
} catch (e) {
console.error(e);
@@ -277,8 +319,8 @@ const CbWidget = {
}
};
CbWidget.init({
- customer_id: 'CUSTOMER_ID', // Replace with Customer id
- product_id: 'PRODUCT_ID', // Replace with product id
+ customer_id: 'aras_shaffer', // Replace with Customer id
+ product_id: 'Dog-food', // Replace with product id
variantSelector: 'select', // select/button
currency: 'USD' // 'USD', 'EUR', etc.,
});