} optionProductLineItems - Option product
+ * line items
+ * @return {string[]} - Product line item option display values, id and name
+ */
+function getLineItemOptionNames(optionProductLineItems) {
+ return collections.map(optionProductLineItems, function (item) {
+ return {
+ displayName: item.productName,
+ optionId: item.optionID,
+ selectedValueId: item.optionValueID
+ };
+ });
+}
+
+/**
+ * Creates the breadcrumbs object
+ * @param {string} cgid - category ID from navigation and search
+ * @param {string} pid - product ID
+ * @param {Array} breadcrumbs - array of breadcrumbs object
+ * @returns {Array} an array of breadcrumb objects
+ */
+function getAllBreadcrumbs(cgid, pid, breadcrumbs) {
+ var URLUtils = require('dw/web/URLUtils');
+ var CatalogMgr = require('dw/catalog/CatalogMgr');
+ var ProductMgr = require('dw/catalog/ProductMgr');
+
+ var category;
+ var product;
+ if (pid) {
+ product = ProductMgr.getProduct(pid);
+ category = product.variant
+ ? product.masterProduct.primaryCategory
+ : product.primaryCategory;
+ } else if (cgid) {
+ category = CatalogMgr.getCategory(cgid);
+ }
+
+ if (category) {
+ breadcrumbs.push({
+ htmlValue: category.displayName,
+ url: URLUtils.url('Search-Show', 'cgid', category.ID)
+ });
+
+ if (category.parent && category.parent.ID !== 'root') {
+ return getAllBreadcrumbs(category.parent.ID, null, breadcrumbs);
+ }
+ }
+
+ return breadcrumbs;
+}
+
+/**
+ * Generates a map of string resources for the template
+ *
+ * @returns {ProductDetailPageResourceMap} - String resource map
+ */
+function getResources() {
+ var Resource = require('dw/web/Resource');
+
+ return {
+ info_selectforstock: Resource.msg(
+ 'info.selectforstock',
+ 'product',
+ 'Select Styles for Availability'
+ ),
+ assistiveSelectedText: Resource.msg('msg.assistive.selected.text', 'common', null)
+ };
+}
+
+/**
+ * Renders the Product Details Page
+ * @param {Object} querystring - query string parameters
+ * @param {Object} reqPageMetaData - request pageMetaData object
+ * @param {Object} usePageDesignerTemplates - wether to use the page designer version of the product detail templates, defaults to false
+ * @returns {Object} contain information needed to render the product page
+ */
+function showProductPage(querystring, reqPageMetaData) {
+ var URLUtils = require('dw/web/URLUtils');
+ var ProductFactory = require('*/cartridge/scripts/factories/product');
+ var pageMetaHelper = require('*/cartridge/scripts/helpers/pageMetaHelper');
+
+ var params = querystring;
+ var product = ProductFactory.get(params);
+ var addToCartUrl = URLUtils.url('Cart-AddProduct');
+ var canonicalUrl = URLUtils.url('Product-Show', 'pid', product.id);
+ var breadcrumbs = getAllBreadcrumbs(null, product.id, []).reverse();
+
+ var template = 'product/productDetails';
+
+ if (product.productType === 'bundle' && !product.template) {
+ template = 'product/bundleDetails';
+ } else if (product.productType === 'set' && !product.template) {
+ template = 'product/setDetails';
+ } else if (product.template) {
+ template = product.template;
+ }
+
+ pageMetaHelper.setPageMetaData(reqPageMetaData, product);
+ pageMetaHelper.setPageMetaTags(reqPageMetaData, product);
+ var schemaData = require('*/cartridge/scripts/helpers/structuredDataHelper').getProductSchema(product);
+
+ return {
+ template: template,
+ product: product,
+ addToCartUrl: addToCartUrl,
+ resources: getResources(),
+ breadcrumbs: breadcrumbs,
+ canonicalUrl: canonicalUrl,
+ schemaData: schemaData
+ };
+}
+
+/**
+ * Retrieves the Product Detail Page, if available in Page Designer
+ * @param {Object} reqProduct - the product as determined from the request
+ * @returns {Object} a lookup result with these fields:
+ * * page - the page that is configured for this product, if any
+ * * invisiblePage - the page that is configured for this product if we ignore visibility, if it is different from page
+ * * aspectAttributes - the aspect attributes that should be passed to the PageMgr, null if no page was found
+ */
+function getPageDesignerProductPage(reqProduct) {
+ if (reqProduct.template) {
+ // this product uses an individual template, for backwards compatibility this has to be handled as a non-PD page
+ return {
+ page: null,
+ invisiblePage: null,
+ aspectAttributes: null
+ };
+ }
+
+ var PageMgr = require('dw/experience/PageMgr');
+ var HashMap = require('dw/util/HashMap');
+
+ var product = reqProduct.raw;
+ if (product === null) {
+ return {
+ page: null,
+ invisiblePage: null,
+ aspectAttributes: null
+ };
+ }
+
+ // determine page on product level, taking precedence over page on category level
+ var lookupProduct = product.variant
+ ? product.masterProduct
+ : product;
+ var page = PageMgr.getPageByProduct(lookupProduct, true, 'pdp');
+ var invisiblePage = PageMgr.getPageByProduct(lookupProduct, false, 'pdp');
+
+ var category = lookupProduct.primaryCategory;
+ if (!category) {
+ category = lookupProduct.classificationCategory;
+ }
+
+ // if no page could be determined on product level try to find it on category level
+ if (!page) {
+ if (category === null) {
+ return {
+ page: null,
+ invisiblePage: invisiblePage,
+ aspectAttributes: null
+ };
+ }
+
+ page = PageMgr.getPageByCategory(category, true, 'pdp');
+
+ if (!invisiblePage) {
+ invisiblePage = PageMgr.getPageByCategory(category, false, 'pdp');
+ }
+ }
+
+ if (page) {
+ var aspectAttributes = new HashMap();
+ aspectAttributes.category = category;
+ aspectAttributes.product = product;
+
+ return {
+ page: page,
+ invisiblePage: page.ID !== invisiblePage.ID ? invisiblePage : null,
+ aspectAttributes: aspectAttributes
+ };
+ }
+
+ return {
+ page: null,
+ invisiblePage: invisiblePage,
+ aspectAttributes: null
+ };
+}
+
+/**
+ * Get product search hit for a given product
+ * @param {dw.catalog.Product} apiProduct - Product instance returned from the API
+ * @returns {dw.catalog.ProductSearchHit} - product search hit for a given product
+ */
+function getProductSearchHit(apiProduct) {
+ var ProductSearchModel = require('dw/catalog/ProductSearchModel');
+ var searchModel = new ProductSearchModel();
+ searchModel.setSearchPhrase(apiProduct.ID);
+ searchModel.search();
+
+ if (searchModel.count === 0) {
+ searchModel.setSearchPhrase(apiProduct.ID.replace(/-/g, ' '));
+ searchModel.search();
+ }
+
+ var hit = searchModel.getProductSearchHit(apiProduct);
+ if (!hit) {
+ var tempHit = searchModel.getProductSearchHits().next();
+ if (tempHit.firstRepresentedProductID === apiProduct.ID) {
+ hit = tempHit;
+ }
+ }
+ return hit;
+}
+
+module.exports = {
+ getOptionValues: getOptionValues,
+ getOptions: getOptions,
+ getCurrentOptionModel: getCurrentOptionModel,
+ getSelectedOptionsUrl: getSelectedOptionsUrl,
+ getProductType: getProductType,
+ getVariationModel: getVariationModel,
+ getConfig: getConfig,
+ getLineItemOptions: getLineItemOptions,
+ getDefaultOptions: getDefaultOptions,
+ getLineItemOptionNames: getLineItemOptionNames,
+ showProductPage: showProductPage,
+ getAllBreadcrumbs: getAllBreadcrumbs,
+ getResources: getResources,
+ getPageDesignerProductPage: getPageDesignerProductPage,
+ getProductSearchHit: getProductSearchHit
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/searchHelpers.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/searchHelpers.js
new file mode 100644
index 0000000..d477a76
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/searchHelpers.js
@@ -0,0 +1,340 @@
+'use strict';
+
+/**
+ * Set search configuration values
+ *
+ * @param {dw.catalog.ProductSearchModel} apiProductSearch - API search instance
+ * @param {Object} params - Provided HTTP query parameters
+ * @return {dw.catalog.ProductSearchModel} - API search instance
+ * @param {Object} httpParameterMap - Query params
+ */
+function setupSearch(apiProductSearch, params, httpParameterMap) {
+ var CatalogMgr = require('dw/catalog/CatalogMgr');
+ var searchModelHelper = require('*/cartridge/scripts/search/search');
+
+ var sortingRule = params.srule ? CatalogMgr.getSortingRule(params.srule) : null;
+ var selectedCategory = CatalogMgr.getCategory(params.cgid);
+ selectedCategory = selectedCategory && selectedCategory.online ? selectedCategory : null;
+
+ searchModelHelper.setProductProperties(apiProductSearch, params, selectedCategory, sortingRule, httpParameterMap);
+
+ if (params.preferences) {
+ searchModelHelper.addRefinementValues(apiProductSearch, params.preferences);
+ }
+
+ return apiProductSearch;
+}
+
+/**
+ * Retrieve a category's template filepath if available
+ *
+ * @param {dw.catalog.ProductSearchModel} apiProductSearch - API search instance
+ * @return {string} - Category's template filepath
+ */
+function getCategoryTemplate(apiProductSearch) {
+ return apiProductSearch.category ? apiProductSearch.category.template : '';
+}
+
+/**
+ * Retrieves the Category Landing Page, if available in Page Designer
+ * @param {Object} categoryID - the category ID as determined from the request
+ * @returns {Object} a lookup result with these fields:
+ * * page - the page that is configured for this category, if any
+ * * invisiblePage - the page that is configured for this category if we ignore visibility, if it is different from page
+ * * aspectAttributes - the aspect attributes that should be passed to the PageMgr, null if no page was found
+ */
+function getPageDesignerCategoryPage(categoryID) {
+ var CatalogMgr = require('dw/catalog/CatalogMgr');
+ var PageMgr = require('dw/experience/PageMgr');
+ var HashMap = require('dw/util/HashMap');
+
+ var category = CatalogMgr.getCategory(categoryID.toLowerCase());
+
+ if (category === null) {
+ return {
+ page: null,
+ invisiblePage: null,
+ aspectAttributes: null
+ };
+ }
+
+ var page = PageMgr.getPageByCategory(category, true, 'plp');
+ var invisiblePage = PageMgr.getPageByCategory(category, false, 'plp');
+
+ if (page) {
+ var aspectAttributes = new HashMap();
+ aspectAttributes.category = category;
+
+ return {
+ page: page,
+ invisiblePage: page.ID !== invisiblePage.ID ? invisiblePage : null,
+ aspectAttributes: aspectAttributes
+ };
+ }
+
+ return {
+ page: null,
+ invisiblePage: invisiblePage,
+ aspectAttributes: null
+ };
+}
+
+/**
+ * Set content search configuration values
+ *
+ * @param {Object} params - Provided HTTP query parameters
+ * @return {Object} - content search instance
+ */
+function setupContentSearch(params) {
+ var ContentSearchModel = require('dw/content/ContentSearchModel');
+ var ContentSearch = require('*/cartridge/models/search/contentSearch');
+ var apiContentSearchModel = new ContentSearchModel();
+
+ apiContentSearchModel.setRecursiveFolderSearch(true);
+ apiContentSearchModel.setFilteredByFolder(false);
+ apiContentSearchModel.setSearchPhrase(params.q);
+ apiContentSearchModel.search();
+ var contentSearchResult = apiContentSearchModel.getContent();
+ var count = Number(apiContentSearchModel.getCount());
+ var contentSearch = new ContentSearch(contentSearchResult, count, params.q, params.startingPage, null);
+
+ return contentSearch;
+}
+
+/**
+ * Set the cache values
+ *
+ * @param {Object} res - The response object
+ */
+function applyCache(res) {
+ res.cachePeriod = 1; // eslint-disable-line no-param-reassign
+ res.cachePeriodUnit = 'hours'; // eslint-disable-line no-param-reassign
+ res.personalized = true; // eslint-disable-line no-param-reassign
+}
+
+/**
+ * performs a search
+ *
+ * @param {Object} req - Provided HTTP query parameters
+ * @param {Object} res - Provided HTTP query parameters
+ * @return {Object} - an object with relevant search information
+ * @param {Object} httpParameterMap - Query params
+ */
+function search(req, res) {
+ var CatalogMgr = require('dw/catalog/CatalogMgr');
+ var URLUtils = require('dw/web/URLUtils');
+ var ProductSearchModel = require('dw/catalog/ProductSearchModel');
+
+ var pageMetaHelper = require('*/cartridge/scripts/helpers/pageMetaHelper');
+ var ProductSearch = require('*/cartridge/models/search/productSearch');
+ var reportingUrlsHelper = require('*/cartridge/scripts/reportingUrls');
+ var schemaHelper = require('*/cartridge/scripts/helpers/structuredDataHelper');
+
+ var apiProductSearch = new ProductSearchModel();
+ var categoryTemplate = '';
+ var maxSlots = 4;
+ var productSearch;
+ var reportingURLs;
+
+ var searchRedirect = req.querystring.q ? apiProductSearch.getSearchRedirect(req.querystring.q) : null;
+
+ if (searchRedirect) {
+ return { searchRedirect: searchRedirect.getLocation() };
+ }
+
+ apiProductSearch = setupSearch(apiProductSearch, req.querystring, req.httpParameterMap);
+ apiProductSearch.search();
+
+ if (!apiProductSearch.personalizedSort) {
+ applyCache(res);
+ }
+ categoryTemplate = getCategoryTemplate(apiProductSearch);
+ productSearch = new ProductSearch(
+ apiProductSearch,
+ req.querystring,
+ req.querystring.srule,
+ CatalogMgr.getSortingOptions(),
+ CatalogMgr.getSiteCatalog().getRoot()
+ );
+
+ pageMetaHelper.setPageMetaTags(req.pageMetaData, productSearch);
+
+ var canonicalUrl = URLUtils.url('Search-Show', 'cgid', req.querystring.cgid);
+ var refineurl = URLUtils.url('Search-Refinebar');
+ var allowedParams = ['q', 'cgid', 'pmin', 'pmax', 'srule', 'pmid'];
+ var isRefinedSearch = false;
+
+ Object.keys(req.querystring).forEach(function (element) {
+ if (allowedParams.indexOf(element) > -1) {
+ refineurl.append(element, req.querystring[element]);
+ }
+
+ if (['pmin', 'pmax'].indexOf(element) > -1) {
+ isRefinedSearch = true;
+ }
+
+ if (element === 'preferences') {
+ var i = 1;
+ isRefinedSearch = true;
+ Object.keys(req.querystring[element]).forEach(function (preference) {
+ refineurl.append('prefn' + i, preference);
+ refineurl.append('prefv' + i, req.querystring[element][preference]);
+ i++;
+ });
+ }
+ });
+
+ if (productSearch.searchKeywords !== null && !isRefinedSearch) {
+ reportingURLs = reportingUrlsHelper.getProductSearchReportingURLs(productSearch);
+ }
+
+ var result = {
+ productSearch: productSearch,
+ maxSlots: maxSlots,
+ reportingURLs: reportingURLs,
+ refineurl: refineurl,
+ canonicalUrl: canonicalUrl,
+ apiProductSearch: apiProductSearch
+ };
+
+ if (productSearch.isCategorySearch && !productSearch.isRefinedCategorySearch && categoryTemplate && apiProductSearch.category.parent.ID === 'root') {
+ pageMetaHelper.setPageMetaData(req.pageMetaData, productSearch.category);
+ result.category = apiProductSearch.category;
+ result.categoryTemplate = categoryTemplate;
+ }
+
+ if (!categoryTemplate || categoryTemplate === 'rendering/category/categoryproducthits') {
+ result.schemaData = schemaHelper.getListingPageSchema(productSearch.productIds);
+ }
+
+ return result;
+}
+
+/**
+ * check to see if we are coming back from a pdp, if yes, use the old qs to set up the grid refinements and number of tiles
+ *
+ * @param {Object} clickStream - object with an array of request to the server in the current session
+ * @return {string} - url to redirect to
+ */
+function backButtonDetection(clickStream) {
+ var preferences = require('*/cartridge/config/preferences');
+ if (!preferences.plpBackButtonOn) {
+ return null;
+ }
+
+ var URLUtils = require('dw/web/URLUtils');
+ var currentClick;
+ var limit = preferences.plpBackButtonLimit || 10;
+ var clicks = clickStream.clicks.reverse().slice(0, limit);
+ var productClick = null;
+ var searchClick = null;
+ var counter = 0;
+ var done = false;
+
+ // find the last pdp click and the last search click
+ var backClicks = clicks.filter(function (click) {
+ if (counter === 0) {
+ currentClick = click;
+ counter++;
+ return true;
+ }
+
+ if (click.pipelineName.indexOf('Product-Show') > -1 && productClick == null && !done) {
+ productClick = click;
+ counter++;
+ return true;
+ }
+
+ if ((click.pipelineName.indexOf('Search-Show') > -1 && searchClick == null)
+ || (click.pipelineName.indexOf('Search-UpdateGrid') > -1 && searchClick == null)
+ || (click.pipelineName.indexOf('Search-ShowAjax') > -1 && searchClick == null)
+ ) {
+ searchClick = click;
+ counter++;
+ done = true;
+ return true;
+ }
+ counter++;
+ return false;
+ });
+
+ if (backClicks.length === 3) {
+ var strCurrent = currentClick.queryString;
+ var strCurrentArray = strCurrent.split('&');
+ var paramCurrentArray = [];
+ var valueCurrentArray = [];
+ var cgidCurrentValue;
+ var qCurrentValue;
+
+ strCurrentArray.forEach(function (strElement) {
+ var strElementSplit = strElement.split('=');
+ if (strElementSplit[0] === 'cgid') { cgidCurrentValue = strElementSplit[1]; }
+ if (strElementSplit[0] === 'q') { qCurrentValue = strElementSplit[1]; }
+ paramCurrentArray.push(strElementSplit[0]);
+ valueCurrentArray.push(strElementSplit[1]);
+ });
+
+ var str = searchClick.queryString;
+ var strArray = str.split('&');
+ var paramArray = [];
+ var valueArray = [];
+ var cgidValue;
+ var qValue;
+ var szPos;
+ var startPos;
+
+ strArray.forEach(function (strElement2, i) {
+ var strElementSplit2 = strElement2.split('=');
+ if (strElementSplit2[0] === 'cgid') { cgidValue = strElementSplit2[1]; }
+ if (strElementSplit2[0] === 'q') { qValue = strElementSplit2[1]; }
+ if (strElementSplit2[0] === 'sz') { szPos = i; }
+ if (strElementSplit2[0] === 'start') { startPos = i; }
+ paramArray.push(strElementSplit2[0]);
+ valueArray.push(decodeURIComponent(strElementSplit2[1]));
+ });
+
+ // alter the sz and start parameters
+ if (!!szPos && !!startPos) {
+ valueArray[szPos] = parseInt(valueArray[startPos], 10) + parseInt(valueArray[szPos], 10);
+ valueArray[startPos] = 0;
+ }
+
+ // check that cgid or q parameter are matching and build url with old parameters
+ if ((cgidCurrentValue && cgidCurrentValue === cgidValue) || (qCurrentValue && qCurrentValue === qValue)) {
+ var redirectGridUrl = URLUtils.url('Search-Show');
+ paramArray.forEach(function (param, i) {
+ redirectGridUrl.append(paramArray[i], valueArray[i]);
+ });
+ return redirectGridUrl.toString();
+ }
+ }
+ return null;
+}
+
+/**
+ * Retrieves banner image URL
+ *
+ * @param {dw.catalog.Category} category - Subject category
+ * @return {string} - Banner's image URL
+ */
+function getBannerImageUrl(category) {
+ var url = null;
+
+ if (category.custom && 'slotBannerImage' in category.custom
+ && category.custom.slotBannerImage) {
+ url = category.custom.slotBannerImage.getURL();
+ } else if (category.image) {
+ url = category.image.getURL();
+ }
+
+ return url;
+}
+
+exports.backButtonDetection = backButtonDetection;
+exports.setupSearch = setupSearch;
+exports.getCategoryTemplate = getCategoryTemplate;
+exports.getPageDesignerCategoryPage = getPageDesignerCategoryPage;
+exports.setupContentSearch = setupContentSearch;
+exports.search = search;
+exports.applyCache = applyCache;
+exports.getBannerImageUrl = getBannerImageUrl;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/storeHelpers.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/storeHelpers.js
new file mode 100644
index 0000000..91a9351
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/storeHelpers.js
@@ -0,0 +1,82 @@
+'use strict';
+
+/**
+ * Searches for stores and creates a plain object of the stores returned by the search
+ * @param {string} radius - selected radius
+ * @param {string} postalCode - postal code for search
+ * @param {string} lat - latitude for search by latitude
+ * @param {string} long - longitude for search by longitude
+ * @param {Object} geolocation - geloaction object with latitude and longitude
+ * @param {boolean} showMap - boolean to show map
+ * @param {dw.web.URL} url - a relative url
+ * @returns {Object} a plain object containing the results of the search
+ */
+function getStores(radius, postalCode, lat, long, geolocation, showMap, url) {
+ var StoresModel = require('*/cartridge/models/stores');
+ var StoreMgr = require('dw/catalog/StoreMgr');
+ var Site = require('dw/system/Site');
+ var URLUtils = require('dw/web/URLUtils');
+
+ var countryCode = geolocation.countryCode;
+ var distanceUnit = countryCode === 'US' ? 'mi' : 'km';
+ var resolvedRadius = radius ? parseInt(radius, 10) : 15;
+
+ var searchKey = {};
+ var storeMgrResult = null;
+ var location = {};
+
+ if (postalCode && postalCode !== '') {
+ // find by postal code
+ searchKey = postalCode;
+ storeMgrResult = StoreMgr.searchStoresByPostalCode(
+ countryCode,
+ searchKey,
+ distanceUnit,
+ resolvedRadius
+ );
+ searchKey = { postalCode: searchKey };
+ } else {
+ // find by coordinates (detect location)
+ location.lat = lat && long ? parseFloat(lat) : geolocation.latitude;
+ location.long = long && lat ? parseFloat(long) : geolocation.longitude;
+
+ storeMgrResult = StoreMgr.searchStoresByCoordinates(location.lat, location.long, distanceUnit, resolvedRadius);
+ searchKey = { lat: location.lat, long: location.long };
+ }
+
+ var actionUrl = url || URLUtils.url('Stores-FindStores', 'showMap', showMap).toString();
+ var apiKey = Site.getCurrent().getCustomPreferenceValue('mapAPI');
+
+ var stores = new StoresModel(storeMgrResult.keySet(), searchKey, resolvedRadius, actionUrl, apiKey);
+
+ return stores;
+}
+
+/**
+ * create the stores results html
+ * @param {Array} storesInfo - an array of objects that contains store information
+ * @returns {string} The rendered HTML
+ */
+function createStoresResultsHtml(storesInfo) {
+ var HashMap = require('dw/util/HashMap');
+ var Template = require('dw/util/Template');
+
+ var context = new HashMap();
+ var object = { stores: { stores: storesInfo } };
+
+ Object.keys(object).forEach(function (key) {
+ context.put(key, object[key]);
+ });
+
+ var template = new Template('storeLocator/storeLocatorResults');
+ return template.render(context).text;
+}
+
+module.exports = {
+ createStoresResultsHtml: createStoresResultsHtml,
+ getStores: getStores
+};
+exports = {
+ createStoresResultsHtml: createStoresResultsHtml,
+ getStores: getStores
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/structuredDataHelper.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/structuredDataHelper.js
new file mode 100644
index 0000000..7b33f06
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/structuredDataHelper.js
@@ -0,0 +1,86 @@
+'use strict';
+
+/**
+ * Get product schema information
+ * @param {Object} product - Product Object
+ *
+ * @returns {Object} - Product Schema object
+ */
+function getProductSchema(product) {
+ var schema = {
+ '@context': 'http://schema.org/',
+ '@type': 'Product',
+ name: product.productName,
+ description: product.shortDescription,
+ mpn: product.id,
+ sku: product.id
+ };
+ if (product.brand) {
+ schema.brand = {
+ '@type': 'Thing',
+ name: product.brand
+ };
+ }
+ if (product.images && product.images.large) {
+ schema.image = [];
+ product.images.large.forEach(function (image) {
+ schema.image.push(image.absURL);
+ });
+ }
+ if (product.price) {
+ schema.offers = {
+ url: require('dw/web/URLUtils').url('Product-Show', 'pid', product.id)
+ };
+ if (product.price.type === 'range') {
+ schema.offers['@type'] = 'AggregateOffer';
+ schema.offers.priceCurrency = product.price.currency;
+ schema.offers.lowprice = product.price.min;
+ schema.offers.highprice = product.price.max;
+ } else {
+ schema.offers['@type'] = 'Offer';
+ if (product.price.sales) {
+ schema.offers.priceCurrency = product.price.sales.currency;
+ schema.offers.price = product.price.sales.decimalPrice;
+ } else if (product.price.list) {
+ schema.offers.priceCurrency = product.price.list.currency;
+ schema.offers.price = product.price.list.decimalPrice;
+ }
+ }
+ schema.offers.availability = 'http://schema.org/InStock';
+ if (product.available) {
+ if (product.availability && product.availability.messages[0] === require('dw/web/Resource').msg('label.preorder', 'common', null)) {
+ schema.offers.availability = 'http://schema.org/PreOrder';
+ }
+ } else {
+ schema.offers.availability = 'http://schema.org/OutOfStock';
+ }
+ }
+ return schema;
+}
+
+/**
+ * Get product listing page schema information
+ * @param {List} productIds - Product Ids
+ *
+ * @returns {Object} - Listing Schema object
+ */
+function getListingPageSchema(productIds) {
+ var schema = {
+ '@context': 'http://schema.org/',
+ '@type': 'ItemList',
+ itemListElement: []
+ };
+ Object.keys(productIds).forEach(function (item) {
+ var productID = productIds[item].productID;
+ schema.itemListElement.push({
+ '@type': 'ListItem',
+ position: Number(item) + 1,
+ url: require('dw/web/URLUtils').abs('Product-Show', 'pid', productID).toString()
+ });
+ });
+ return schema;
+}
+module.exports = {
+ getProductSchema: getProductSchema,
+ getListingPageSchema: getListingPageSchema
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/urlHelpers.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/urlHelpers.js
new file mode 100644
index 0000000..b6be1fb
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/helpers/urlHelpers.js
@@ -0,0 +1,44 @@
+'use strict';
+
+var QueryString = require('server').querystring;
+
+/**
+ * Appends query params to a URL
+ *
+ * @param {string} target - An URL endpoint with or without query params
+ * e.g. http://someresource or http://someresource?xyz=123
+ * @param {Object|string[]} queryParamsToAppend - Object or list of query params strings with or
+ * without resource path prepended, where resource path is discarded
+ * e.g., ['xyz=123'] or ['http://someresource?xyz=123'] or { var1: val1, var2: val2 }
+ * @return {string} - Target URL appended with query params from sourceQueryString
+ */
+function appendQueryParams(target, queryParamsToAppend) {
+ var resource = target.split('?')[0];
+ var targetQueryString = new QueryString(target);
+
+ if (!Array.isArray(queryParamsToAppend) && typeof queryParamsToAppend === 'object') {
+ Object.keys(queryParamsToAppend).forEach(function (key) {
+ var value = queryParamsToAppend[key];
+ if (!Object.prototype.hasOwnProperty.call(targetQueryString, key) && value) {
+ targetQueryString[key] = queryParamsToAppend[key] || '';
+ }
+ });
+ } else {
+ targetQueryString = queryParamsToAppend.reduce(function (current, params) {
+ var currentCopy = current;
+ var queryToAppend = new QueryString(params);
+ Object.keys(queryToAppend).forEach(function (key) {
+ if (!Object.prototype.hasOwnProperty.call(currentCopy, key)) {
+ currentCopy[key] = queryToAppend[key] || '';
+ }
+ });
+ return currentCopy;
+ }, targetQueryString);
+ }
+ return resource + '?' + targetQueryString.toString();
+}
+
+module.exports = {
+ QueryString: QueryString,
+ appendQueryParams: appendQueryParams
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/cart/calculate.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/cart/calculate.js
new file mode 100644
index 0000000..484ae1e
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/cart/calculate.js
@@ -0,0 +1,278 @@
+/* eslint-disable */
+'use strict';
+
+/** @module calculate */
+/**
+ * This javascript file implements methods (via Common.js exports) that are needed by
+ * the new (smaller) CalculateCart.ds script file. This allows OCAPI calls to reference
+ * these tools via the OCAPI 'hook' mechanism
+ */
+
+var HashMap = require('dw/util/HashMap');
+var PromotionMgr = require('dw/campaign/PromotionMgr');
+var ShippingMgr = require('dw/order/ShippingMgr');
+var ShippingLocation = require('dw/order/ShippingLocation');
+var TaxMgr = require('dw/order/TaxMgr');
+var Logger = require('dw/system/Logger');
+var Status = require('dw/system/Status');
+var HookMgr = require('dw/system/HookMgr');
+var collections = require('*/cartridge/scripts/util/collections');
+
+/**
+ * @function calculate
+ *
+ * calculate is the arching logic for computing the value of a basket. It makes
+ * calls into cart/calculate.js and enables both SG and OCAPI applications to share
+ * the same cart calculation logic.
+ *
+ * @param {object} basket The basket to be calculated
+ */
+exports.calculate = function (basket) {
+ // ===================================================
+ // ===== CALCULATE PRODUCT LINE ITEM PRICES =====
+ // ===================================================
+
+ calculateProductPrices(basket);
+
+ // ===================================================
+ // ===== CALCULATE GIFT CERTIFICATE PRICES =====
+ // ===================================================
+
+ calculateGiftCertificatePrices(basket);
+
+ // ===================================================
+ // ===== Note: Promotions must be applied =====
+ // ===== after the tax calculation for =====
+ // ===== storefronts based on GROSS prices =====
+ // ===================================================
+
+ // ===================================================
+ // ===== APPLY PROMOTION DISCOUNTS =====
+ // ===== Apply product and order promotions. =====
+ // ===== Must be done before shipping =====
+ // ===== calculation. =====
+ // ===================================================
+
+ PromotionMgr.applyDiscounts(basket);
+
+ // ===================================================
+ // ===== CALCULATE SHIPPING COSTS =====
+ // ===================================================
+
+ // apply product specific shipping costs
+ // and calculate total shipping costs
+ HookMgr.callHook('dw.order.calculateShipping', 'calculateShipping', basket);
+
+ // ===================================================
+ // ===== APPLY PROMOTION DISCOUNTS =====
+ // ===== Apply product and order and =====
+ // ===== shipping promotions. =====
+ // ===================================================
+
+ PromotionMgr.applyDiscounts(basket);
+
+ // since we might have bonus product line items, we need to
+ // reset product prices
+ calculateProductPrices(basket);
+
+ // ===================================================
+ // ===== CALCULATE TAX =====
+ // ===================================================
+
+ HookMgr.callHook('dw.order.calculateTax', 'calculateTax', basket);
+
+ // ===================================================
+ // ===== CALCULATE BASKET TOTALS =====
+ // ===================================================
+
+ basket.updateTotals();
+
+ // ===================================================
+ // ===== DONE =====
+ // ===================================================
+
+ return new Status(Status.OK);
+};
+
+/**
+ * @function calculateProductPrices
+ *
+ * Calculates product prices based on line item quantities. Set calculates prices
+ * on the product line items. This updates the basket and returns nothing
+ *
+ * @param {object} basket The basket containing the elements to be computed
+ */
+function calculateProductPrices (basket) {
+ // get total quantities for all products contained in the basket
+ var productQuantities = basket.getProductQuantities();
+ var productQuantitiesIt = productQuantities.keySet().iterator();
+
+ // get product prices for the accumulated product quantities
+ var productPrices = new HashMap();
+
+ while (productQuantitiesIt.hasNext()) {
+ var prod = productQuantitiesIt.next();
+ var quantity = productQuantities.get(prod);
+ productPrices.put(prod, prod.priceModel.getPrice(quantity));
+ }
+
+ // iterate all product line items of the basket and set prices
+ var productLineItems = basket.getAllProductLineItems().iterator();
+ while (productLineItems.hasNext()) {
+ var productLineItem = productLineItems.next();
+
+ // handle non-catalog products
+ if (!productLineItem.catalogProduct) {
+ productLineItem.setPriceValue(productLineItem.basePrice.valueOrNull);
+ continue;
+ }
+
+ var product = productLineItem.product;
+
+ // handle option line items
+ if (productLineItem.optionProductLineItem) {
+ // for bonus option line items, we do not update the price
+ // the price is set to 0.0 by the promotion engine
+ if (!productLineItem.bonusProductLineItem) {
+ productLineItem.updateOptionPrice();
+ }
+ // handle bundle line items, but only if they're not a bonus
+ } else if (productLineItem.bundledProductLineItem) {
+ // no price is set for bundled product line items
+ // handle bonus line items
+ // the promotion engine set the price of a bonus product to 0.0
+ // we update this price here to the actual product price just to
+ // provide the total customer savings in the storefront
+ // we have to update the product price as well as the bonus adjustment
+ } else if (productLineItem.bonusProductLineItem && product !== null) {
+ var price = product.priceModel.price;
+ var adjustedPrice = productLineItem.adjustedPrice;
+ productLineItem.setPriceValue(price.valueOrNull);
+ // get the product quantity
+ var quantity2 = productLineItem.quantity;
+ // we assume that a bonus line item has only one price adjustment
+ var adjustments = productLineItem.priceAdjustments;
+ if (!adjustments.isEmpty()) {
+ var adjustment = adjustments.iterator().next();
+ var adjustmentPrice = price.multiply(quantity2.value).multiply(-1.0).add(adjustedPrice);
+ adjustment.setPriceValue(adjustmentPrice.valueOrNull);
+ }
+
+
+ // set the product price. Updates the 'basePrice' of the product line item,
+ // and either the 'netPrice' or the 'grossPrice' based on the current taxation
+ // policy
+
+ // handle product line items unrelated to product
+ } else if (product === null) {
+ productLineItem.setPriceValue(null);
+ // handle normal product line items
+ } else {
+ productLineItem.setPriceValue(productPrices.get(product).valueOrNull);
+ }
+ }
+}
+
+/**
+ * @function calculateGiftCertificates
+ *
+ * Function sets either the net or gross price attribute of all gift certificate
+ * line items of the basket by using the gift certificate base price. It updates the basket in place.
+ *
+ * @param {object} basket The basket containing the gift certificates
+ */
+function calculateGiftCertificatePrices (basket) {
+ var giftCertificates = basket.getGiftCertificateLineItems().iterator();
+ while (giftCertificates.hasNext()) {
+ var giftCertificate = giftCertificates.next();
+ giftCertificate.setPriceValue(giftCertificate.basePrice.valueOrNull);
+ }
+}
+
+exports.calculateShipping = function(basket) {
+ ShippingMgr.applyShippingCost(basket);
+ return new Status(Status.OK);
+}
+
+/**
+ * @function calculateTax
+ *
+ * Determines tax rates for all line items of the basket. Uses the shipping addresses
+ * associated with the basket shipments to determine the appropriate tax jurisdiction.
+ * Uses the tax class assigned to products and shipping methods to lookup tax rates.
+ *
+ * Sets the tax-related fields of the line items.
+ *
+ * Handles gift certificates, which aren't taxable.
+ *
+ * Note that the function implements a fallback to the default tax jurisdiction
+ * if no other jurisdiction matches the specified shipping location/shipping address.
+ *
+ * Note that the function implements a fallback to the default tax class if a
+ * product or a shipping method does explicitly define a tax class.
+ *
+ * @param {dw.order.Basket} basket The basket containing the elements for which taxes need to be calculated
+ */
+exports.calculateTax = function(basket) {
+ var basketCalculationHelpers = require('*/cartridge/scripts/helpers/basketCalculationHelpers');
+
+ var taxes = basketCalculationHelpers.calculateTaxes(basket);
+
+ // convert taxes into hashmap for performance.
+ var taxesMap = {};
+
+ taxes.taxes.forEach(function (item) {
+ taxesMap[item.uuid] = { value: item.value, amount: item.amount };
+ });
+
+ var lineItems = basket.getAllLineItems();
+
+ var totalShippingGrossPrice = 0;
+ var totalShippingNetPrice = 0;
+
+ // update taxes for all line items
+ collections.forEach(lineItems, function (lineItem) {
+ var tax = taxesMap[lineItem.UUID];
+
+ if (tax) {
+ if (tax.amount) {
+ lineItem.updateTaxAmount(tax.value);
+ if (lineItem instanceof dw.order.ShippingLineItem) {
+ totalShippingGrossPrice += lineItem.getAdjustedGrossPrice();
+ totalShippingNetPrice += lineItem.getAdjustedNetPrice();
+ }
+ } else {
+ lineItem.updateTax(tax.value);
+ }
+ } else {
+ if (lineItem.taxClassID === TaxMgr.customRateTaxClassID) {
+ // do not touch tax rate for fix rate items
+ lineItem.updateTax(lineItem.taxRate);
+ } else {
+ // otherwise reset taxes to null
+ lineItem.updateTax(null);
+ }
+ }
+ });
+
+ // besides shipment line items, we need to calculate tax for possible order-level price adjustments
+ // this includes order-level shipping price adjustments
+ if (!basket.getPriceAdjustments().empty || !basket.getShippingPriceAdjustments().empty) {
+ if (collections.first(basket.getPriceAdjustments(), function (priceAdjustment) {
+ return taxesMap[priceAdjustment.UUID] === null;
+ }) || collections.first(basket.getShippingPriceAdjustments(), function (shippingPriceAdjustment) {
+ return taxesMap[shippingPriceAdjustment.UUID] === null;
+ })) {
+ basket.updateOrderLevelPriceAdjustmentTax();
+ }
+ }
+
+ // if hook returned custom properties attach them to the order model
+ if (taxes.custom) {
+ Object.keys(taxes.custom).forEach(function (key) {
+ basket.custom[key] = taxes.custom[key];
+ });
+ }
+
+ return new Status(Status.OK);
+}
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/fraudDetection.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/fraudDetection.js
new file mode 100644
index 0000000..2a5b142
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/fraudDetection.js
@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * fraud detection hook
+ * @param {dw.order.Order} order - The order object to be placed
+ * @returns {Object} an error object. Status can have three values 'success', 'fail' or 'flag'
+ * error code that could be mapped to localized resources error Message a string with the
+ * error message, that could be used as a fall-back if error code doesn't have a mapping
+ */
+function fraudDetection(order) { // eslint-disable-line
+ var errorCode;
+ var errorMessage;
+ var status = 'success';
+
+ return {
+ status: status,
+ errorCode: errorCode,
+ errorMessage: errorMessage
+ };
+}
+
+exports.fraudDetection = fraudDetection;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/basic_credit.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/basic_credit.js
new file mode 100644
index 0000000..92ee909
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/basic_credit.js
@@ -0,0 +1,156 @@
+'use strict';
+
+var collections = require('*/cartridge/scripts/util/collections');
+
+var PaymentInstrument = require('dw/order/PaymentInstrument');
+var PaymentMgr = require('dw/order/PaymentMgr');
+var PaymentStatusCodes = require('dw/order/PaymentStatusCodes');
+var Resource = require('dw/web/Resource');
+var Transaction = require('dw/system/Transaction');
+
+/**
+ * Creates a token. This should be replaced by utilizing a tokenization provider
+ * @returns {string} a token
+ */
+function createToken() {
+ return Math.random().toString(36).substr(2);
+}
+
+/**
+ * Verifies that entered credit card information is a valid card. If the information is valid a
+ * credit card payment instrument is created
+ * @param {dw.order.Basket} basket Current users's basket
+ * @param {Object} paymentInformation - the payment information
+ * @param {string} paymentMethodID - paymentmethodID
+ * @param {Object} req the request object
+ * @return {Object} returns an error object
+ */
+function Handle(basket, paymentInformation, paymentMethodID, req) {
+ var currentBasket = basket;
+ var cardErrors = {};
+ var cardNumber = paymentInformation.cardNumber.value;
+ var cardSecurityCode = paymentInformation.securityCode.value;
+ var expirationMonth = paymentInformation.expirationMonth.value;
+ var expirationYear = paymentInformation.expirationYear.value;
+ var serverErrors = [];
+ var creditCardStatus;
+
+ var cardType = paymentInformation.cardType.value;
+ var paymentCard = PaymentMgr.getPaymentCard(cardType);
+
+ // Validate payment instrument
+ if (paymentMethodID === PaymentInstrument.METHOD_CREDIT_CARD) {
+ var creditCardPaymentMethod = PaymentMgr.getPaymentMethod(PaymentInstrument.METHOD_CREDIT_CARD);
+ var paymentCardValue = PaymentMgr.getPaymentCard(paymentInformation.cardType.value);
+
+ var applicablePaymentCards = creditCardPaymentMethod.getApplicablePaymentCards(
+ req.currentCustomer.raw,
+ req.geolocation.countryCode,
+ null
+ );
+
+ if (!applicablePaymentCards.contains(paymentCardValue)) {
+ // Invalid Payment Instrument
+ var invalidPaymentMethod = Resource.msg('error.payment.not.valid', 'checkout', null);
+ return { fieldErrors: [], serverErrors: [invalidPaymentMethod], error: true };
+ }
+ }
+
+ if (!paymentInformation.creditCardToken) {
+ if (paymentCard) {
+ creditCardStatus = paymentCard.verify(
+ expirationMonth,
+ expirationYear,
+ cardNumber,
+ cardSecurityCode
+ );
+ } else {
+ cardErrors[paymentInformation.cardNumber.htmlName] = Resource.msg('error.invalid.card.number', 'creditCard', null);
+
+ return { fieldErrors: [cardErrors], serverErrors: serverErrors, error: true };
+ }
+
+ if (creditCardStatus.error) {
+ collections.forEach(creditCardStatus.items, function (item) {
+ switch (item.code) {
+ case PaymentStatusCodes.CREDITCARD_INVALID_CARD_NUMBER:
+ cardErrors[paymentInformation.cardNumber.htmlName] = Resource.msg('error.invalid.card.number', 'creditCard', null);
+ break;
+
+ case PaymentStatusCodes.CREDITCARD_INVALID_EXPIRATION_DATE:
+ cardErrors[paymentInformation.expirationMonth.htmlName] = Resource.msg('error.expired.credit.card', 'creditCard', null);
+ cardErrors[paymentInformation.expirationYear.htmlName] = Resource.msg('error.expired.credit.card', 'creditCard', null);
+ break;
+
+ case PaymentStatusCodes.CREDITCARD_INVALID_SECURITY_CODE:
+ cardErrors[paymentInformation.securityCode.htmlName] = Resource.msg('error.invalid.security.code', 'creditCard', null);
+ break;
+ default:
+ serverErrors.push(
+ Resource.msg('error.card.information.error', 'creditCard', null)
+ );
+ }
+ });
+
+ return { fieldErrors: [cardErrors], serverErrors: serverErrors, error: true };
+ }
+ }
+
+ Transaction.wrap(function () {
+ var paymentInstruments = currentBasket.getPaymentInstruments(
+ PaymentInstrument.METHOD_CREDIT_CARD
+ );
+
+ collections.forEach(paymentInstruments, function (item) {
+ currentBasket.removePaymentInstrument(item);
+ });
+
+ var paymentInstrument = currentBasket.createPaymentInstrument(PaymentInstrument.METHOD_CREDIT_CARD, currentBasket.totalGrossPrice);
+
+ paymentInstrument.setCreditCardHolder(currentBasket.billingAddress.fullName);
+ paymentInstrument.setCreditCardNumber(cardNumber);
+ paymentInstrument.setCreditCardType(cardType);
+ paymentInstrument.setCreditCardExpirationMonth(expirationMonth);
+ paymentInstrument.setCreditCardExpirationYear(expirationYear);
+ paymentInstrument.setCreditCardToken(
+ paymentInformation.creditCardToken
+ ? paymentInformation.creditCardToken
+ : createToken()
+ );
+ });
+
+ return { fieldErrors: cardErrors, serverErrors: serverErrors, error: false };
+}
+
+/**
+ * Authorizes a payment using a credit card. Customizations may use other processors and custom
+ * logic to authorize credit card payment.
+ * @param {number} orderNumber - The current order's number
+ * @param {dw.order.PaymentInstrument} paymentInstrument - The payment instrument to authorize
+ * @param {dw.order.PaymentProcessor} paymentProcessor - The payment processor of the current
+ * payment method
+ * @return {Object} returns an error object
+ */
+function Authorize(orderNumber, paymentInstrument, paymentProcessor) {
+ var serverErrors = [];
+ var fieldErrors = {};
+ var error = false;
+
+ try {
+ Transaction.wrap(function () {
+ paymentInstrument.paymentTransaction.setTransactionID(orderNumber);
+ paymentInstrument.paymentTransaction.setPaymentProcessor(paymentProcessor);
+ });
+ } catch (e) {
+ error = true;
+ serverErrors.push(
+ Resource.msg('error.technical', 'checkout', null)
+ );
+ }
+
+ return { fieldErrors: fieldErrors, serverErrors: serverErrors, error: error };
+}
+
+exports.Handle = Handle;
+exports.Authorize = Authorize;
+exports.createToken = createToken;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/basic_credit_form_processor.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/basic_credit_form_processor.js
new file mode 100644
index 0000000..7744831
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/basic_credit_form_processor.js
@@ -0,0 +1,135 @@
+'use strict';
+
+var COHelpers = require('*/cartridge/scripts/checkout/checkoutHelpers');
+
+/**
+ * Verifies the required information for billing form is provided.
+ * @param {Object} req - The request object
+ * @param {Object} paymentForm - the payment form
+ * @param {Object} viewFormData - object contains billing form data
+ * @returns {Object} an object that has error information or payment information
+ */
+function processForm(req, paymentForm, viewFormData) {
+ var array = require('*/cartridge/scripts/util/array');
+
+ var viewData = viewFormData;
+ var creditCardErrors = {};
+
+ if (!req.form.storedPaymentUUID) {
+ // verify credit card form data
+ creditCardErrors = COHelpers.validateCreditCard(paymentForm);
+ }
+
+ if (Object.keys(creditCardErrors).length) {
+ return {
+ fieldErrors: creditCardErrors,
+ error: true
+ };
+ }
+
+ viewData.paymentMethod = {
+ value: paymentForm.paymentMethod.value,
+ htmlName: paymentForm.paymentMethod.value
+ };
+
+ viewData.paymentInformation = {
+ cardType: {
+ value: paymentForm.creditCardFields.cardType.value,
+ htmlName: paymentForm.creditCardFields.cardType.htmlName
+ },
+ cardNumber: {
+ value: paymentForm.creditCardFields.cardNumber.value,
+ htmlName: paymentForm.creditCardFields.cardNumber.htmlName
+ },
+ securityCode: {
+ value: paymentForm.creditCardFields.securityCode.value,
+ htmlName: paymentForm.creditCardFields.securityCode.htmlName
+ },
+ expirationMonth: {
+ value: parseInt(
+ paymentForm.creditCardFields.expirationMonth.selectedOption,
+ 10
+ ),
+ htmlName: paymentForm.creditCardFields.expirationMonth.htmlName
+ },
+ expirationYear: {
+ value: parseInt(paymentForm.creditCardFields.expirationYear.value, 10),
+ htmlName: paymentForm.creditCardFields.expirationYear.htmlName
+ }
+ };
+
+ if (req.form.storedPaymentUUID) {
+ viewData.storedPaymentUUID = req.form.storedPaymentUUID;
+ }
+
+ viewData.saveCard = paymentForm.creditCardFields.saveCard.checked;
+
+ // process payment information
+ if (viewData.storedPaymentUUID
+ && req.currentCustomer.raw.authenticated
+ && req.currentCustomer.raw.registered
+ ) {
+ var paymentInstruments = req.currentCustomer.wallet.paymentInstruments;
+ var paymentInstrument = array.find(paymentInstruments, function (item) {
+ return viewData.storedPaymentUUID === item.UUID;
+ });
+
+ viewData.paymentInformation.cardNumber.value = paymentInstrument.creditCardNumber;
+ viewData.paymentInformation.cardType.value = paymentInstrument.creditCardType;
+ viewData.paymentInformation.securityCode.value = req.form.securityCode;
+ viewData.paymentInformation.expirationMonth.value = paymentInstrument.creditCardExpirationMonth;
+ viewData.paymentInformation.expirationYear.value = paymentInstrument.creditCardExpirationYear;
+ viewData.paymentInformation.creditCardToken = paymentInstrument.raw.creditCardToken;
+ }
+
+ return {
+ error: false,
+ viewData: viewData
+ };
+}
+
+/**
+ * Save the credit card information to login account if save card option is selected
+ * @param {Object} req - The request object
+ * @param {dw.order.Basket} basket - The current basket
+ * @param {Object} billingData - payment information
+ */
+function savePaymentInformation(req, basket, billingData) {
+ var CustomerMgr = require('dw/customer/CustomerMgr');
+
+ if (!billingData.storedPaymentUUID
+ && req.currentCustomer.raw.authenticated
+ && req.currentCustomer.raw.registered
+ && billingData.saveCard
+ && (billingData.paymentMethod.value === 'CREDIT_CARD')
+ ) {
+ var customer = CustomerMgr.getCustomerByCustomerNumber(
+ req.currentCustomer.profile.customerNo
+ );
+
+ var saveCardResult = COHelpers.savePaymentInstrumentToWallet(
+ billingData,
+ basket,
+ customer
+ );
+
+ req.currentCustomer.wallet.paymentInstruments.push({
+ creditCardHolder: saveCardResult.creditCardHolder,
+ maskedCreditCardNumber: saveCardResult.maskedCreditCardNumber,
+ creditCardType: saveCardResult.creditCardType,
+ creditCardExpirationMonth: saveCardResult.creditCardExpirationMonth,
+ creditCardExpirationYear: saveCardResult.creditCardExpirationYear,
+ UUID: saveCardResult.UUID,
+ creditCardNumber: Object.hasOwnProperty.call(
+ saveCardResult,
+ 'creditCardNumber'
+ )
+ ? saveCardResult.creditCardNumber
+ : null,
+ raw: saveCardResult
+ });
+ }
+}
+
+exports.processForm = processForm;
+exports.savePaymentInformation = savePaymentInformation;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/default.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/default.js
new file mode 100644
index 0000000..e5b2a66
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/default.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var Resource = require('dw/web/Resource');
+
+/**
+ * default hook if no payment processor is supported
+ * @return {Object} an object that contains error information
+ */
+function Handle() {
+ var errors = [];
+ errors.push(Resource.msg('error.payment.processor.not.supported', 'checkout', null));
+ return { fieldErrors: [], serverErrors: errors, error: true };
+}
+
+/**
+ * default hook if no payment processor is supported
+ * @return {Object} an object that contains error information
+ */
+function Authorize() {
+ var errors = [];
+ errors.push(Resource.msg('error.payment.processor.not.supported', 'checkout', null));
+ return { fieldErrors: [], serverErrors: errors, error: true };
+}
+
+exports.Handle = Handle;
+exports.Authorize = Authorize;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/default_form_processor.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/default_form_processor.js
new file mode 100644
index 0000000..fd75c53
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/payment/processor/default_form_processor.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var Resource = require('dw/web/Resource');
+
+/**
+ * default hook if no payment form processor is supported
+ * @return {Object} an object that contains error information
+ */
+function processForm() {
+ var errors = [];
+ errors.push(Resource.msg('error.payment.processor.not.supported', 'checkout', null));
+ return { fieldErrors: [], serverErrors: errors, error: true };
+}
+
+/**
+ * default hook if no save payment information processor is supported
+ */
+function savePaymentInformation() {
+
+}
+
+exports.processForm = processForm;
+exports.savePaymentInformation = savePaymentInformation;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/postAuthorizationHandling.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/postAuthorizationHandling.js
new file mode 100644
index 0000000..279a358
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/postAuthorizationHandling.js
@@ -0,0 +1,13 @@
+'use strict';
+
+// Dummy implementation
+
+/**
+ * This function is to handle the post payment authorization customizations
+ * @param {Object} result - Authorization Result
+ */
+function postAuthorization(result) { // eslint-disable-line no-unused-vars
+
+}
+
+exports.postAuthorization = postAuthorization;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/taxes.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/taxes.js
new file mode 100644
index 0000000..32d7450
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/taxes.js
@@ -0,0 +1,88 @@
+'use strict';
+
+var collections = require('*/cartridge/scripts/util/collections');
+var ShippingLocation = require('dw/order/ShippingLocation');
+var TaxMgr = require('dw/order/TaxMgr');
+var Logger = require('dw/system/Logger');
+
+/**
+ * @typedef {Object} TaxField
+ * @property {string} UUID - ID of the line item
+ * @property {number|dw.value.Money} value - Either Tax Code or Tax Amount that should be applied to the line item.
+ * @property {boolean} [amount=false] - Boolean indicating whether value field contains Tax Amount (true) or Tax Rate (false).
+ */
+
+/**
+ * @typedef {Object} Response
+ * @property {Array} taxes - List of taxes to line items UUIDs to be applied to the order
+ * @property {Object} custom - List of custom properties to be attached to the basket
+ */
+
+/**
+ * Calculate sales taxes
+ * @param {dw.order.Basket} basket - current basket
+ * @returns {Response} - An object that contains calculated taxes and custom properties
+ */
+function calculateTaxes(basket) {
+ var taxes = [];
+
+ var shipments = basket.getShipments();
+ collections.forEach(shipments, function (shipment) {
+ var taxJurisdictionId = null;
+
+ if (shipment.shippingAddress) {
+ var location = new ShippingLocation(shipment.shippingAddress);
+ taxJurisdictionId = TaxMgr.getTaxJurisdictionID(location);
+ }
+
+ if (!taxJurisdictionId) {
+ taxJurisdictionId = TaxMgr.defaultTaxJurisdictionID;
+ }
+
+ // if we have no tax jurisdiction, we cannot calculate tax
+ if (!taxJurisdictionId) {
+ return;
+ }
+
+ var lineItems = shipment.getAllLineItems();
+
+ collections.forEach(lineItems, function (lineItem) {
+ var taxClassId = lineItem.taxClassID;
+
+ Logger.debug('1. Line Item {0} with Tax Class {1} and Tax Rate {2}', lineItem.lineItemText, lineItem.taxClassID, lineItem.taxRate);
+
+ // do not touch line items with fix tax rate
+ if (taxClassId === TaxMgr.customRateTaxClassID) {
+ return;
+ }
+
+ // line item does not define a valid tax class; let's fall back to default tax class
+ if (!taxClassId) {
+ taxClassId = TaxMgr.defaultTaxClassID;
+ }
+
+ // if we have no tax class, we cannot calculate tax
+ if (!taxClassId) {
+ Logger.error('Line Item {0} has invalid Tax Class {1}', lineItem.lineItemText, lineItem.taxClassID);
+ return;
+ }
+
+ // get the tax rate
+ var taxRate = TaxMgr.getTaxRate(taxClassId, taxJurisdictionId);
+ // w/o a valid tax rate, we cannot calculate tax for the line item
+ if (!taxRate && taxRate !== 0) {
+ return;
+ }
+
+ // calculate the tax of the line item
+ taxes.push({ uuid: lineItem.UUID, value: taxRate, amount: false });
+ Logger.debug('2. Line Item {0} with Tax Class {1} and Tax Rate {2}', lineItem.lineItemText, lineItem.taxClassID, lineItem.taxRate);
+ });
+ });
+
+ return { taxes: taxes, custom: {} };
+}
+
+module.exports = {
+ calculateTaxes: calculateTaxes
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/validateBasket.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/validateBasket.js
new file mode 100644
index 0000000..970e95c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/validateBasket.js
@@ -0,0 +1,47 @@
+'use strict';
+
+var Resource = require('dw/web/Resource');
+var validationHelpers = require('*/cartridge/scripts/helpers/basketValidationHelpers');
+
+/**
+ * validates the current users basket
+ * @param {dw.order.Basket} basket - The current user's basket
+ * @param {boolean} validateTax - boolean that determines whether or not to validate taxes
+ * @returns {Object} an error object
+ */
+function validateBasket(basket, validateTax) {
+ var result = { error: false, message: null };
+
+ if (!basket) {
+ result.error = true;
+ result.message = Resource.msg('error.cart.expired', 'cart', null);
+ } else {
+ var productExistence = validationHelpers.validateProducts(basket);
+ var validCoupons = validationHelpers.validateCoupons(basket);
+ var totalTax = true;
+
+ if (validateTax) {
+ totalTax = basket.totalTax.available;
+ }
+
+ if (productExistence.error || !productExistence.hasInventory) {
+ result.error = true;
+ result.message = Resource.msg('error.cart.or.checkout.error', 'cart', null);
+ } else if (validCoupons.error) {
+ result.error = true;
+ result.message = Resource.msg('error.invalid.coupon', 'cart', null);
+ } else if (basket.productLineItems.getLength() === 0) {
+ result.error = true;
+ } else if (!basket.merchandizeTotalPrice.available) {
+ result.error = true;
+ result.message = Resource.msg('error.cart.or.checkout.error', 'cart', null);
+ } else if (!totalTax) {
+ result.error = true;
+ result.message = Resource.msg('error.invalid.tax', 'cart', null);
+ }
+ }
+
+ return result;
+}
+
+exports.validateBasket = validateBasket;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/validateOrder.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/validateOrder.js
new file mode 100644
index 0000000..345a072
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/hooks/validateOrder.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var Resource = require('dw/web/Resource');
+var validationHelpers = require('*/cartridge/scripts/helpers/basketValidationHelpers');
+
+/**
+ * validates the current users basket
+ * @param {dw.order.Basket} basket - The current user's basket
+ * @param {boolean} validateTax - boolean that determines whether or not to validate taxes
+ * @returns {Object} an error object
+ */
+function validateOrder(basket, validateTax) {
+ var result = { error: false, message: null };
+
+ if (!basket) {
+ result.error = true;
+ result.message = Resource.msg('error.cart.expired', 'cart', null);
+ } else {
+ var productExistence = validationHelpers.validateProducts(basket);
+ var validCoupons = validationHelpers.validateCoupons(basket);
+ var validShipments = validationHelpers.validateShipments(basket);
+ var totalTax = true;
+
+ if (validateTax) {
+ totalTax = basket.totalTax.available;
+ }
+
+ if (productExistence.error || !productExistence.hasInventory) {
+ result.error = true;
+ result.message = Resource.msg('error.cart.or.checkout.error', 'cart', null);
+ } else if (validCoupons.error) {
+ result.error = true;
+ result.message = Resource.msg('error.invalid.coupon', 'cart', null);
+ } else if (basket.productLineItems.getLength() === 0) {
+ result.error = true;
+ } else if (!basket.merchandizeTotalPrice.available) {
+ result.error = true;
+ result.message = Resource.msg('error.cart.or.checkout.error', 'cart', null);
+ } else if (!totalTax) {
+ result.error = true;
+ result.message = Resource.msg('error.invalid.tax', 'cart', null);
+ } else if (!validShipments) {
+ result.error = true;
+ result.message = Resource.msg('error.card.invalid.shipments', 'cart', null);
+ }
+ }
+
+ return result;
+}
+
+exports.validateOrder = validateOrder;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/cache.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/cache.js
new file mode 100644
index 0000000..dc2c11f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/cache.js
@@ -0,0 +1,62 @@
+'use strict';
+
+/**
+ * Applies the default expiration value for the page cache.
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+*/
+function applyDefaultCache(req, res, next) {
+ res.cachePeriod = 24; // eslint-disable-line no-param-reassign
+ res.cachePeriodUnit = 'hours'; // eslint-disable-line no-param-reassign
+ next();
+}
+
+/**
+ * Applies the default price promotion page cache.
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+*/
+function applyPromotionSensitiveCache(req, res, next) {
+ res.cachePeriod = 24; // eslint-disable-line no-param-reassign
+ res.cachePeriodUnit = 'hours'; // eslint-disable-line no-param-reassign
+ res.personalized = true; // eslint-disable-line no-param-reassign
+ next();
+}
+
+/**
+ * Applies the default price promotion page cache.
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function applyShortPromotionSensitiveCache(req, res, next) {
+ res.cachePeriod = 1; // eslint-disable-line no-param-reassign
+ res.cachePeriodUnit = 'hours'; // eslint-disable-line no-param-reassign
+ res.personalized = true; // eslint-disable-line no-param-reassign
+ next();
+}
+
+/**
+ * Applies the inventory sensitive page cache.
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+*/
+function applyInventorySensitiveCache(req, res, next) {
+ res.cachePeriod = 30; // eslint-disable-line no-param-reassign
+ res.cachePeriodUnit = 'minutes'; // eslint-disable-line no-param-reassign
+ next();
+}
+
+module.exports = {
+ applyDefaultCache: applyDefaultCache,
+ applyPromotionSensitiveCache: applyPromotionSensitiveCache,
+ applyInventorySensitiveCache: applyInventorySensitiveCache,
+ applyShortPromotionSensitiveCache: applyShortPromotionSensitiveCache
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/consentTracking.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/consentTracking.js
new file mode 100644
index 0000000..c1ef9fb
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/consentTracking.js
@@ -0,0 +1,39 @@
+'use strict';
+
+var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
+
+/**
+ * Middleware to use consent tracking check. Uses CSRF middleware to generate a CSRF token if one is not present.
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function consent(req, res, next) {
+ var consented = req.session.privacyCache.get('consent');
+ if (consented === undefined) {
+ consented = null;
+ req.session.privacyCache.set('consent', consented);
+ } else if (typeof consented === 'boolean') {
+ req.session.raw.setTrackingAllowed(consented);
+ }
+
+ res.setViewData({ tracking_consent: consented });
+
+ // The endpoint ConsentTracking-SetConsent requires a CSRF token.
+ // It replaced ConsentTracking-SetSession, which didn't require a CSRF token,
+ // so this middleware is commonly used alongside `csrfProtection.generateToken`,
+ // rather than used exclusively. The order in which the two methods are executed
+ // is not guaranteed, so both methods check for the existence of `csrf` on the
+ // view data to avoid unnecessary work.
+ var viewData = res.getViewData();
+ if (viewData.csrf) {
+ next();
+ } else {
+ csrfProtection.generateToken(req, res, next);
+ }
+}
+
+module.exports = {
+ consent: consent
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/csrf.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/csrf.js
new file mode 100644
index 0000000..873977c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/csrf.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var csrfProtection = require('dw/web/CSRFProtection');
+var CustomerMgr = require('dw/customer/CustomerMgr');
+var URLUtils = require('dw/web/URLUtils');
+
+/**
+ * Middleware validating CSRF token
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function validateRequest(req, res, next) {
+ if (!csrfProtection.validateRequest()) {
+ CustomerMgr.logoutCustomer(false);
+ res.redirect(URLUtils.url('CSRF-Fail'));
+ }
+
+ next();
+}
+
+/**
+ * Middleware validating CSRF token from ajax request
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function validateAjaxRequest(req, res, next) {
+ if (!csrfProtection.validateRequest()) {
+ CustomerMgr.logoutCustomer(false);
+ res.redirect(URLUtils.url('CSRF-AjaxFail'));
+ }
+
+ next();
+}
+
+/**
+ * Middleware generating a CSRF token and setting the view data. Only executes if a CSRF token is not already on the view data.
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function generateToken(req, res, next) {
+ // The consent tracking middleware calls this method because it requires a CSRF token.
+ // It didn't always require a CSRF token, so it is commonly listed alongside this middleware,
+ // rather than used exclusively. The order in which the two methods are executed is not
+ // guaranteed, so both methods check for the existence of `csrf` on the view data to avoid
+ // unnecessary work.
+ var viewData = res.getViewData();
+ if (!viewData.csrf) {
+ res.setViewData({
+ csrf: {
+ tokenName: csrfProtection.getTokenName(),
+ token: csrfProtection.generateToken()
+ }
+ });
+ }
+
+ next();
+}
+
+module.exports = {
+ validateRequest: validateRequest,
+ validateAjaxRequest: validateAjaxRequest,
+ generateToken: generateToken
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/pageMetaData.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/pageMetaData.js
new file mode 100644
index 0000000..d5ec0f7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/pageMetaData.js
@@ -0,0 +1,38 @@
+'use strict';
+
+/**
+ * Middleware to compute request pageMetaData object
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function computedPageMetaData(req, res, next) {
+ var computedMetaData = {
+ title: req.pageMetaData.title,
+ description: req.pageMetaData.description,
+ keywords: req.pageMetaData.keywords,
+ pageMetaTags: []
+ };
+
+ req.pageMetaData.pageMetaTags.forEach(function (item) {
+ if (item.title) {
+ computedMetaData.title = item.content;
+ } else if (item.name && item.ID === 'description') {
+ computedMetaData.description = item.content;
+ } else if (item.name && item.ID === 'keywords') {
+ computedMetaData.keywords = item.content;
+ } else {
+ computedMetaData.pageMetaTags.push(item);
+ }
+ });
+
+ res.setViewData({
+ CurrentPageMetaData: computedMetaData
+ });
+ next();
+}
+
+module.exports = {
+ computedPageMetaData: computedPageMetaData
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/userLoggedIn.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/userLoggedIn.js
new file mode 100644
index 0000000..5acd333
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/middleware/userLoggedIn.js
@@ -0,0 +1,56 @@
+'use strict';
+
+var URLUtils = require('dw/web/URLUtils');
+
+/**
+ * Middleware validating if user logged in
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function validateLoggedIn(req, res, next) {
+ if (!req.currentCustomer.profile) {
+ if (req.querystring.args) {
+ req.session.privacyCache.set('args', req.querystring.args);
+ }
+
+ var target = req.querystring.rurl || 1;
+
+ res.redirect(URLUtils.url('Login-Show', 'rurl', target));
+ }
+ next();
+}
+
+/**
+ * Middleware validating if user logged in from ajax request
+ * @param {Object} req - Request object
+ * @param {Object} res - Response object
+ * @param {Function} next - Next call in the middleware chain
+ * @returns {void}
+ */
+function validateLoggedInAjax(req, res, next) {
+ if (!req.currentCustomer.profile) {
+ if (req.querystring.args) {
+ req.session.privacyCache.set('args', req.querystring.args);
+ }
+
+ var target = req.querystring.rurl || 1;
+
+ res.setStatusCode(500);
+ res.setViewData({
+ loggedin: false,
+ redirectUrl: URLUtils.url('Login-Show', 'rurl', target).toString()
+ });
+ } else {
+ res.setViewData({
+ loggedin: true
+ });
+ }
+ next();
+}
+
+module.exports = {
+ validateLoggedIn: validateLoggedIn,
+ validateLoggedInAjax: validateLoggedInAjax
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/order/orderHelpers.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/order/orderHelpers.js
new file mode 100644
index 0000000..92b9902
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/order/orderHelpers.js
@@ -0,0 +1,154 @@
+'use strict';
+
+var OrderMgr = require('dw/order/OrderMgr');
+var Order = require('dw/order/Order');
+var Resource = require('dw/web/Resource');
+var URLUtils = require('dw/web/URLUtils');
+var Locale = require('dw/util/Locale');
+
+var OrderModel = require('*/cartridge/models/order');
+
+/**
+* Returns a list of orders for the current customer
+* @param {Object} currentCustomer - object with customer properties
+* @param {Object} querystring - querystring properties
+* @param {string} locale - the current request's locale id
+* @returns {Object} - orderModel of the current dw order object
+* */
+function getOrders(currentCustomer, querystring, locale) {
+ // get all orders for this user
+ var orderHistory = currentCustomer.raw.getOrderHistory();
+ var customerOrders = orderHistory.getOrders(
+ 'status!={0}',
+ 'creationDate desc',
+ Order.ORDER_STATUS_REPLACED
+ );
+
+ var orders = [];
+
+ var filterValues = [
+ {
+ displayValue: Resource.msg('orderhistory.sixmonths.option', 'order', null),
+ optionValue: URLUtils.url('Order-Filtered', 'orderFilter', '6').abs().toString()
+ },
+ {
+ displayValue: Resource.msg('orderhistory.twelvemonths.option', 'order', null),
+ optionValue: URLUtils.url('Order-Filtered', 'orderFilter', '12').abs().toString()
+ }
+ ];
+ var orderYear;
+ var years = [];
+ var customerOrder;
+ var SIX_MONTHS_AGO = Date.now() - 15778476000;
+ var YEAR_AGO = Date.now() - 31556952000;
+ var orderModel;
+
+ var currentLocale = Locale.getLocale(locale);
+
+ while (customerOrders.hasNext()) {
+ customerOrder = customerOrders.next();
+ var config = {
+ numberOfLineItems: 'single'
+ };
+
+ orderYear = customerOrder.getCreationDate().getFullYear().toString();
+ var orderTime = customerOrder.getCreationDate().getTime();
+
+ if (years.indexOf(orderYear) === -1) {
+ var optionURL = URLUtils.url('Order-Filtered', 'orderFilter', orderYear).abs().toString();
+ filterValues.push({
+ displayValue: orderYear,
+ optionValue: optionURL
+ });
+ years.push(orderYear);
+ }
+
+ if (querystring.orderFilter
+ && querystring.orderFilter !== '12'
+ && querystring.orderFilter !== '6') {
+ if (orderYear === querystring.orderFilter) {
+ orderModel = new OrderModel(
+ customerOrder,
+ { config: config, countryCode: currentLocale.country }
+ );
+ orders.push(orderModel);
+ }
+ } else if (querystring.orderFilter
+ && YEAR_AGO < orderTime
+ && querystring.orderFilter === '12') {
+ orderModel = new OrderModel(
+ customerOrder,
+ { config: config, countryCode: currentLocale.country }
+ );
+ orders.push(orderModel);
+ } else if (SIX_MONTHS_AGO < orderTime) {
+ orderModel = new OrderModel(
+ customerOrder,
+ { config: config, countryCode: currentLocale.country }
+ );
+ orders.push(orderModel);
+ }
+ }
+
+ return {
+ orders: orders,
+ filterValues: filterValues
+ };
+}
+
+/**
+ * Creates an order model for the current customer
+ * @param {Object} req - the request object
+ * @returns {Object} an object of the customer's last order
+ */
+function getLastOrder(req) {
+ var orderModel = null;
+ var orderHistory = req.currentCustomer.raw.getOrderHistory();
+ var customerOrders = orderHistory.getOrders(
+ 'status!={0}',
+ 'creationDate desc',
+ Order.ORDER_STATUS_REPLACED
+ );
+
+ var order = customerOrders.first();
+
+ if (order) {
+ var currentLocale = Locale.getLocale(req.locale.id);
+
+ var config = {
+ numberOfLineItems: 'single'
+ };
+
+ orderModel = new OrderModel(order, { config: config, countryCode: currentLocale.country });
+ }
+
+ return orderModel;
+}
+
+/**
+ * Creates an order model for the current customer
+ * @param {Object} req - the request object
+ * @returns {Object} an object of the customer's order
+ */
+function getOrderDetails(req) {
+ var order = OrderMgr.getOrder(req.querystring.orderID);
+
+ var config = {
+ numberOfLineItems: '*'
+ };
+
+ var currentLocale = Locale.getLocale(req.locale.id);
+
+ var orderModel = new OrderModel(
+ order,
+ { config: config, countryCode: currentLocale.country, containerView: 'order' }
+ );
+
+ return orderModel;
+}
+
+module.exports = {
+ getOrders: getOrders,
+ getLastOrder: getLastOrder,
+ getOrderDetails: getOrderDetails
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/renderTemplateHelper.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/renderTemplateHelper.js
new file mode 100644
index 0000000..c66a4a2
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/renderTemplateHelper.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var HashMap = require('dw/util/HashMap');
+var Template = require('dw/util/Template');
+
+/**
+ * gets the render html for the given isml template
+ * @param {Object} templateContext - object that will fill template placeholders
+ * @param {string} templateName - the name of the isml template to render.
+ * @returns {string} the rendered isml.
+ */
+function getRenderedHtml(templateContext, templateName) {
+ var context = new HashMap();
+
+ Object.keys(templateContext).forEach(function (key) {
+ context.put(key, templateContext[key]);
+ });
+
+ var template = new Template(templateName);
+ return template.render(context).text;
+}
+
+module.exports = {
+ getRenderedHtml: getRenderedHtml
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/reportingUrls.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/reportingUrls.js
new file mode 100644
index 0000000..cf9c978
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/reportingUrls.js
@@ -0,0 +1,373 @@
+'use strict';
+
+var Calendar = require('dw/util/Calendar');
+var URLUtils = require('dw/web/URLUtils');
+var StringUtils = require('dw/util/StringUtils');
+
+var collections = require('*/cartridge/scripts/util/collections');
+var formatHelpers = require('*/cartridge/scripts/helpers/formatHelpers');
+
+/**
+ * get item promo reporting urls
+ * @param {dw.order.ProductLineItem} productLineItem - the target product line item
+ * @param {Array} reportingURLs - current array of reporting urls
+ * @returns {Array} - an array of urls
+ */
+function getItemPromoReportingURLs(productLineItem, reportingURLs) {
+ var result = reportingURLs;
+ // Each item can have multiple price adjustments
+ collections.forEach(productLineItem.priceAdjustments, function (PLIPriceAdjustments) {
+ var ItemPromo = URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'ItemPromo',
+ 'ItemUUID',
+ productLineItem.UUID,
+ 'campID',
+ PLIPriceAdjustments.campaignID
+ ? PLIPriceAdjustments.campaignID
+ : 'N/A',
+ 'promoID',
+ PLIPriceAdjustments.promotionID,
+ 'value',
+ formatHelpers.formatPrice(PLIPriceAdjustments.price.value),
+ 'campaign',
+ !PLIPriceAdjustments.isCustom(),
+ 'coupon',
+ PLIPriceAdjustments.basedOnCoupon
+ );
+
+ result.push(ItemPromo);
+ });
+
+ return result;
+}
+
+/**
+ * get shipping promo reporting urls
+ * @param {dw.order.Shipment} shipment - the target shipment
+ * @param {Array} reportingURLs - current array of reporting urls
+ * @returns {Array} - an array of urls
+ */
+function getShippingPromoReportingURLs(shipment, reportingURLs) {
+ var result = reportingURLs;
+
+ // The shipment might have one or more price adjustments
+ collections.forEach(shipment.shippingPriceAdjustments, function (shippingPriceAdjustment) {
+ var shippingPromoUrl = URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'ShippingPromo',
+ 'campID',
+ shippingPriceAdjustment.campaignID
+ ? shippingPriceAdjustment.campaignID
+ : 'N/A',
+ 'promoID',
+ shippingPriceAdjustment.promotionID,
+ 'value',
+ formatHelpers.formatPrice(shippingPriceAdjustment.price.value),
+ 'campaign',
+ !shippingPriceAdjustment.isCustom(),
+ 'coupon',
+ shippingPriceAdjustment.basedOnCoupon
+ );
+
+ result.push(shippingPromoUrl);
+ });
+
+ return result;
+}
+
+/**
+ * get order promo reporting urls
+ * @param {dw.util.Collection} priceAdjustments - the price adjustments for the line item container
+ * @param {Array} reportingURLs - current array of reporting urls
+ * @returns {Array} - an array of urls
+ */
+function getOrderPromoReportingURLs(priceAdjustments, reportingURLs) {
+ var result = reportingURLs;
+
+ // Report all price adjustments for the entire order, such as 25% of entire order.
+ collections.forEach(priceAdjustments, function (priceAdjustment) {
+ var OrderPromoUrl = URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'OrderPromo',
+ 'campID',
+ priceAdjustment.campaignID ? priceAdjustment.campaignID : 'N/A',
+ 'promoID',
+ priceAdjustment.promotionID,
+ 'value',
+ formatHelpers.formatPrice(priceAdjustment.price.value),
+ 'campaign',
+ !priceAdjustment.isCustom(),
+ 'coupon',
+ priceAdjustment.basedOnCoupon
+ );
+
+ result.push(OrderPromoUrl);
+ });
+
+ return result;
+}
+
+/**
+ * Build the urls that report on the order
+ * @param {dw.order.Basket} currentBasket - the basket object
+ * @returns {Array} - an array of urls that are used to report on the current basket
+ */
+function getBasketOpenReportingURLs(currentBasket) {
+ var reportingURLs = [];
+
+ // Some common statistic for this cart
+ var basketOpen = URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'BasketOpen',
+ 'BasketID',
+ currentBasket.UUID,
+ 'Items',
+ formatHelpers.formatNumber(currentBasket.productLineItems.length),
+ 'MerchandizeTotal',
+ formatHelpers.formatPrice(
+ currentBasket.merchandizeTotalPrice.value
+ ),
+ 'AdjMerchandizeTotal',
+ formatHelpers.formatPrice(
+ currentBasket.getAdjustedMerchandizeTotalPrice(false).value
+ ),
+ 'AdjMerchandizeTotalPromo',
+ formatHelpers.formatPrice(
+ currentBasket.getAdjustedMerchandizeTotalPrice(true).value
+ ),
+ 'ShippingTotal',
+ formatHelpers.formatPrice(currentBasket.shippingTotalPrice.value),
+ 'AdjShippingTotal',
+ formatHelpers.formatPrice(
+ currentBasket.adjustedShippingTotalPrice.value
+ )
+ );
+
+ reportingURLs.push(basketOpen);
+
+ // Report all price adjustments for the entire cart
+ reportingURLs = getOrderPromoReportingURLs(currentBasket.priceAdjustments, reportingURLs);
+
+ // Check all shipments for shipping promotions, lineitems and their promotions
+ collections.forEach(currentBasket.shipments, function (shipment) {
+ // The shipment might have one or more price adjustments
+ reportingURLs = getShippingPromoReportingURLs(shipment, reportingURLs);
+
+ // Each item can have multiple price adjustments
+ collections.forEach(shipment.productLineItems, function (productLineItem) {
+ reportingURLs = getItemPromoReportingURLs(productLineItem, reportingURLs);
+ });
+ });
+
+ return reportingURLs;
+}
+
+/**
+ * Build the urls that report on order placed by a customer
+ * @param {number} orderCount - The number of orders this customer placed in the system
+ * @param {Array} reportingURLs - current array of reporting urls
+ * @returns {Array} - an array of urls that are used to report
+ */
+function getOrderPlacedReportingURLs(orderCount, reportingURLs) {
+ var result = reportingURLs;
+
+ result.push(URLUtils.url('ReportingEvent-Start', 'ID', 'UserOrders', 'Count', formatHelpers.formatNumber(orderCount)));
+
+ return result;
+}
+
+/**
+ * Build the urls that report on the order
+ * @param {dw.order.Order} order - the order object
+ * @returns {Array} - an array of urls that are used to report on the current order
+ */
+function getOrderReportingURLs(order) {
+ var reportingURLs = [];
+
+ // Report the general information about the order
+ var orderEvent = URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'Order',
+ 'CurrencyCode',
+ order.currencyCode,
+ 'CreationDate',
+ StringUtils.formatCalendar(new Calendar(order.creationDate), 'yyyyMMdd\'T\'HH:mm:ss.SSSZ'),
+ 'CreatedBy',
+ order.createdBy,
+ 'MerchandizeTotalNet',
+ formatHelpers.formatPrice(order.merchandizeTotalNetPrice.value),
+ 'MerchandizeTotalTax',
+ formatHelpers.formatPrice(order.merchandizeTotalTax.value),
+ 'MerchandizeTotalGross',
+ formatHelpers.formatPrice(order.merchandizeTotalGrossPrice.value),
+ 'ShippingNet',
+ formatHelpers.formatPrice(order.shippingTotalNetPrice.value),
+ 'ShippingTax',
+ formatHelpers.formatPrice(order.shippingTotalTax.value),
+ 'ShippingGross',
+ formatHelpers.formatPrice(order.shippingTotalGrossPrice.value),
+ 'AdjMerchandizeTotalNet',
+ formatHelpers.formatPrice(
+ order.adjustedMerchandizeTotalNetPrice.value
+ ),
+ 'AdjMerchandizeTotalTax',
+ formatHelpers.formatPrice(
+ order.adjustedMerchandizeTotalTax.value
+ ),
+ 'AdjMerchandizeTotalGross',
+ formatHelpers.formatPrice(
+ order.adjustedMerchandizeTotalGrossPrice.value
+ ),
+ 'AdjShippingNet',
+ formatHelpers.formatPrice(order.adjustedShippingTotalNetPrice.value),
+ 'AdjShippingTax',
+ formatHelpers.formatPrice(order.adjustedShippingTotalTax.value),
+ 'AdjShippingGross',
+ formatHelpers.formatPrice(order.adjustedShippingTotalGrossPrice.value),
+ 'Net',
+ formatHelpers.formatPrice(order.totalNetPrice.value),
+ 'Tax',
+ formatHelpers.formatPrice(order.totalTax.value),
+ 'Gross',
+ formatHelpers.formatPrice(order.totalGrossPrice.value)
+ );
+
+ reportingURLs.push(orderEvent);
+
+ // Report all price adjustments for the entire order
+ reportingURLs = getOrderPromoReportingURLs(order.priceAdjustments, reportingURLs);
+ reportingURLs = getOrderPlacedReportingURLs(order.customer.orderHistory.orderCount, reportingURLs);
+
+ // Check all shipments for shipping promotions, lineitems and their promotions
+ collections.forEach(order.shipments, function (shipment) {
+ // The shipment might have one or more price adjustments
+ reportingURLs = getShippingPromoReportingURLs(shipment, reportingURLs);
+
+ // Log event for each product line item
+ collections.forEach(shipment.productLineItems, function (productLineItem) {
+ var itemUrl = URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'Item',
+ 'SKU',
+ productLineItem.productID,
+ 'Name',
+ productLineItem.productName,
+ 'UUID',
+ productLineItem.UUID,
+ 'Quantity',
+ formatHelpers.formatNumber(productLineItem.quantity.value),
+ 'CurrencyCode',
+ order.currencyCode,
+ 'Base',
+ formatHelpers.formatPrice(productLineItem.basePrice.value),
+ 'Net',
+ formatHelpers.formatPrice(productLineItem.netPrice.value),
+ 'Tax',
+ formatHelpers.formatPrice(productLineItem.tax.value),
+ 'Gross',
+ formatHelpers.formatPrice(productLineItem.grossPrice.value),
+ 'AdjNet',
+ formatHelpers.formatPrice(productLineItem.adjustedNetPrice.value),
+ 'AdjTax',
+ formatHelpers.formatPrice(productLineItem.adjustedTax.value),
+ 'AdjGross',
+ formatHelpers.formatPrice(productLineItem.adjustedGrossPrice.value),
+ 'Mfg',
+ productLineItem.manufacturerName,
+ 'Bonus',
+ productLineItem.bonusProductLineItem
+ );
+
+ reportingURLs.push(itemUrl);
+
+ // Each item can have multiple price adjustments
+ reportingURLs = getItemPromoReportingURLs(productLineItem, reportingURLs);
+ });
+ });
+
+ return reportingURLs;
+}
+
+/**
+ * Build the urls that report on the product search
+ * @param {Object} productSearch - product search model
+ * @returns {Array} - an array of urls that are used to report
+ */
+function getProductSearchReportingURLs(productSearch) {
+ var result = [];
+
+ result.push(URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'ProductSearch',
+ 'Phrase',
+ productSearch.searchKeywords,
+ 'ResultCount',
+ formatHelpers.formatNumber(productSearch.count)
+ ));
+
+ return result;
+}
+
+/**
+ * Build the urls that report on the checkout
+ * @param {string} UUID - the target basket's uuid
+ * @param {number} step - the step in the checkout
+ * @param {string} stepName - the name of the step in checkout
+ * @returns {Array} - an array of urls that are used to report
+ */
+function getCheckoutReportingURLs(UUID, step, stepName) {
+ var result = [];
+
+ result.push(URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'Checkout',
+ 'BasketID',
+ UUID,
+ 'Step',
+ formatHelpers.formatNumber(step),
+ 'Name',
+ stepName
+ ));
+
+ return result;
+}
+
+/**
+ * Build the urls that report on account open
+ * @param {number} registeredCustomerCount - The number of registered customers in the system
+ * @returns {Array} - an array of urls that are used to report
+ */
+function getAccountOpenReportingURLs(registeredCustomerCount) {
+ var result = [];
+
+ result.push(URLUtils.url(
+ 'ReportingEvent-Start',
+ 'ID',
+ 'AccountOpen',
+ 'TotalUserCount',
+ formatHelpers.formatNumber(registeredCustomerCount)
+ ));
+
+ return result;
+}
+
+module.exports = {
+ getItemPromoReportingURLs: getItemPromoReportingURLs,
+ getShippingPromoReportingURLs: getShippingPromoReportingURLs,
+ getOrderPromoReportingURLs: getOrderPromoReportingURLs,
+ getBasketOpenReportingURLs: getBasketOpenReportingURLs,
+ getOrderReportingURLs: getOrderReportingURLs,
+ getProductSearchReportingURLs: getProductSearchReportingURLs,
+ getCheckoutReportingURLs: getCheckoutReportingURLs,
+ getAccountOpenReportingURLs: getAccountOpenReportingURLs,
+ getOrderPlacedReportingURLs: getOrderPlacedReportingURLs
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/search/search.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/search/search.js
new file mode 100644
index 0000000..c471e3a
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/search/search.js
@@ -0,0 +1,61 @@
+'use strict';
+
+/**
+ * Sets the relevant product search model properties, depending on the parameters provided
+ *
+ * @param {dw.catalog.ProductSearchModel} productSearch - Product search object
+ * @param {Object} httpParams - Query params
+ * @param {dw.catalog.Category} selectedCategory - Selected category
+ * @param {dw.catalog.SortingRule} sortingRule - Product grid sort rule
+ * @param {Object} httpParameterMap - Query params
+ * @property {Double} [httpParameterMap.pmin] - Minimum Price
+ * @property {Double} [httpParameterMap.pmax] - Maximum Price
+ */
+function setProductProperties(productSearch, httpParams, selectedCategory, sortingRule, httpParameterMap) {
+ var searchPhrase;
+
+ if (httpParams.q) {
+ searchPhrase = httpParams.q;
+ productSearch.setSearchPhrase(searchPhrase);
+ }
+ if (selectedCategory) {
+ productSearch.setCategoryID(selectedCategory.ID);
+ }
+ if (httpParams.pid) {
+ productSearch.setProductIDs([httpParams.pid]);
+ }
+ if (httpParameterMap) {
+ if (httpParameterMap.pmin) {
+ productSearch.setPriceMin(httpParameterMap.pmin.doubleValue);
+ }
+ if (httpParameterMap.pmax) {
+ productSearch.setPriceMax(httpParameterMap.pmax.doubleValue);
+ }
+ }
+ if (httpParams.pmid) {
+ productSearch.setPromotionID(httpParams.pmid);
+ }
+
+ if (sortingRule) {
+ productSearch.setSortingRule(sortingRule);
+ }
+
+ productSearch.setRecursiveCategorySearch(true);
+}
+
+/**
+ * Updates the search model with the preference refinement values
+ *
+ * @param {dw.catalog.SearchModel} search - SearchModel instance
+ * @param {Object} preferences - Query params map
+ */
+function addRefinementValues(search, preferences) {
+ Object.keys(preferences).forEach(function (key) {
+ search.addRefinementValues(key, preferences[key]);
+ });
+}
+
+module.exports = {
+ addRefinementValues: addRefinementValues,
+ setProductProperties: setProductProperties
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/theming/skin.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/theming/skin.js
new file mode 100644
index 0000000..09ce933
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/theming/skin.js
@@ -0,0 +1,36 @@
+'use strict';
+
+/**
+ * This script creates a base implementation to handle configurable theming options within SFRA.
+ * It is an extensible anchor point for more elaborate implementations and can be overwritten by plugins and custom implementation
+ */
+
+var Template = require('dw/util/Template');
+var HashMap = require('dw/util/HashMap');
+
+var ContentMgr = require('dw/content/ContentMgr');
+
+/**
+ * This function is called within the html head and renders its output there.
+ * It gathers either a css file from a content asset for external skin.css or the body element inline for quicker critical css introduction
+ * @returns {string} text to be rendered
+ */
+exports.renderSkin = function renderSkin() {
+ // get content asset
+ var contentAsset = ContentMgr.getContent('store-skin');
+ var renderParameters = new HashMap();
+ if (contentAsset) {
+ // check for file or inline css
+ if (contentAsset.custom.customCSSFile) {
+ renderParameters.type = 'FILE';
+ renderParameters.url = contentAsset.custom.customCSSFile.getURL();
+ } else {
+ renderParameters.type = 'INLINE';
+ renderParameters.markup = contentAsset.custom.body;
+ }
+ var template = new Template('components/skin');
+ return template.render(renderParameters).text;
+ }
+ // render nothing if no skin is maintained in custom installation
+ return '';
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/array.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/array.js
new file mode 100644
index 0000000..6320818
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/array.js
@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * Returns a value of the first element in the array that satisfies the provided testing function.
+ * Otherwise undefined is returned.
+ * @param {Array} array - Array of elements to find the match in.
+ * @param {Array} matcher - function that returns true if match is found
+ * @return {Object|undefined} element that matches provided testing function or undefined.
+ */
+function find(array, matcher) {
+ for (var i = 0, l = array.length; i < l; i++) {
+ if (matcher(array[i], i)) {
+ return array[i];
+ }
+ }
+
+ return undefined;
+}
+
+module.exports = {
+ find: find
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/collections.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/collections.js
new file mode 100644
index 0000000..ef74a51
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/collections.js
@@ -0,0 +1,187 @@
+'use strict';
+
+var ArrayList = require('dw/util/ArrayList');
+
+/**
+ * Map method for dw.util.Collection subclass instance
+ * @param {dw.util.Collection} collection - Collection subclass instance to map over
+ * @param {Function} callback - Callback function for each item
+ * @param {Object} [scope] - Optional execution scope to pass to callback
+ * @returns {Array} Array of results of map
+ */
+function map(collection, callback, scope) {
+ var iterator = Object.hasOwnProperty.call(collection, 'iterator')
+ ? collection.iterator()
+ : collection;
+ var index = 0;
+ var item = null;
+ var result = [];
+ while (iterator.hasNext()) {
+ item = iterator.next();
+ result.push(scope ? callback.call(scope, item, index, collection)
+ : callback(item, index, collection));
+ index++;
+ }
+ return result;
+}
+
+/**
+ * forEach method for dw.util.Collection subclass instances
+ * @param {dw.util.Collection} collection - Collection subclass instance to map over
+ * @param {Function} callback - Callback function for each item
+ * @param {Object} [scope] - Optional execution scope to pass to callback
+ * @returns {void}
+ */
+function forEach(collection, callback, scope) {
+ var iterator = collection.iterator();
+ var index = 0;
+ var item = null;
+ while (iterator.hasNext()) {
+ item = iterator.next();
+ if (scope) {
+ callback.call(scope, item, index, collection);
+ } else {
+ callback(item, index, collection);
+ }
+ index++;
+ }
+}
+
+/**
+ * concat method for dw.util.Collection subclass instances
+ * @param {...dw.util.Collection} arguments - first collection to concatinate
+ * @return {dw.util.ArrayList} ArrayList containing all passed collections
+ */
+function concat() {
+ var result = new ArrayList();
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ result.addAll(arguments[i]);
+ }
+ return result;
+}
+
+/**
+ * reduce method for dw.util.Collection subclass instances
+ * @param {dw.util.Collection} collection - Collection subclass instance to reduce
+ * @param {Function} callback - Function to execute on each value in the array
+ * @return {Object} result of the execution of callback function on all items
+ */
+function reduce(collection, callback) {
+ if (typeof callback !== 'function') {
+ throw new TypeError(callback + ' is not a function');
+ }
+
+ var value;
+ var index = 1;
+ var iterator = collection.iterator();
+
+ if (arguments.length === 3) {
+ value = arguments[2];
+ index = 0;
+ } else if (iterator.hasNext() && (collection.getLength() !== 1)) {
+ value = iterator.next();
+ }
+
+ if (collection.getLength() === 0 && !value) {
+ throw new TypeError('Reduce of empty array with no initial value');
+ }
+
+ if ((collection.getLength() === 1 && !value) || (collection.getLength() === 0 && value)) {
+ return collection.getLength() === 1 ? iterator.next() : value;
+ }
+
+ while (iterator.hasNext()) {
+ var item = iterator.next();
+ value = callback(value, item, index, collection);
+ index++;
+ }
+
+ return value;
+}
+
+/**
+ * Pluck method for dw.util.Collection subclass instance
+ * @param {dw.util.Collection|dw.util.Iterator} list - Collection subclass or Iterator instance to
+ * pluck from
+ * @param {string} property - Object property to pluck
+ * @returns {Array} Array of results of plucked properties
+ */
+function pluck(list, property) {
+ var result = [];
+ var iterator = Object.hasOwnProperty.call(list, 'iterator') ? list.iterator() : list;
+ while (iterator.hasNext()) {
+ var temp = iterator.next();
+ if (temp[property]) {
+ result.push(temp[property]);
+ }
+ }
+ return result;
+}
+
+/**
+ * Find method for dw.util.Collection subclass instance
+ * @param {dw.util.Collection} collection - Collection subclass instance to find value in
+ * @param {Function} match - Match function
+ * @param {Object} [scope] - Optional execution scope to pass to the match function
+ * @returns {Object|null} Single item from the collection
+ */
+function find(collection, match, scope) {
+ var result = null;
+
+ if (collection) {
+ var iterator = collection.iterator();
+ while (iterator.hasNext()) {
+ var item = iterator.next();
+ if (scope ? match.call(scope, item) : match(item)) {
+ result = item;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Gets the first item from dw.util.Collection subclass instance
+ * @param {dw.util.Colleciton} collection - Collection subclass instance to work with
+ * @return {Object|null} First element from the collection
+ */
+function first(collection) {
+ var iterator = collection.iterator();
+ return iterator.hasNext() ? iterator.next() : null;
+}
+
+/**
+ * Determines whether every list item meets callback's truthy conditional
+ *
+ * @param {dw.util.Collection} collection - Collection subclass instance to map over
+ * @param {Function} callback - Callback function for each item
+ * @return {boolean} - Whether every list item meets callback's truthy conditional
+ */
+function every(collection, callback) {
+ if (typeof callback !== 'function') {
+ throw new TypeError(callback + ' is not a function');
+ }
+
+ var iterator = collection.iterator();
+ while (iterator.hasNext()) {
+ var item = iterator.next();
+
+ if (!callback(item)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+module.exports = {
+ map: map,
+ forEach: forEach,
+ concat: concat,
+ reduce: reduce,
+ pluck: pluck,
+ find: find,
+ first: first,
+ every: every
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/formatting.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/formatting.js
new file mode 100644
index 0000000..1a4cecf
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/formatting.js
@@ -0,0 +1,18 @@
+'use strict';
+
+var formatMoney = require('dw/util/StringUtils').formatMoney;
+var Money = require('dw/value/Money');
+
+/**
+ * Returns the string representation of an amount, using specified currencyCode/countryCode
+ * @param {number} value - the currency value
+ * @param {string} currencyCode - the currency code
+ * @return {string} formatted currency string
+ */
+function formatCurrency(value, currencyCode) {
+ return formatMoney(new Money(value, currencyCode));
+}
+
+module.exports = {
+ formatCurrency: formatCurrency
+};
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/promotionCache.js b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/promotionCache.js
new file mode 100644
index 0000000..70cb001
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/promotionCache.js
@@ -0,0 +1,35 @@
+/* globals session request */
+
+'use strict';
+
+var PromotionMgr = require('dw/campaign/PromotionMgr');
+var collections = require('*/cartridge/scripts/util/collections');
+
+var promotionCache = Object.create(null);
+
+Object.defineProperty(promotionCache, 'promotions', {
+ get: function () {
+ // will parse the id from the main request and use that as cache key for the promotion cache as the main goal is to ensure that
+ // the promotions are just calculated once for the main request (Search) and not all the remote includes.
+ // If a new main request comes in it should not be used as eventually applicable promotions have been changed linked to the user.
+ var cacheKey = request.requestID.split('-')[0];
+ if (session.privacy.promoCache) {
+ var cacheObj = JSON.parse(session.privacy.promoCache);
+ if (cacheKey === cacheObj.cacheKey) {
+ return cacheObj.promoIds;
+ }
+ }
+ var activePromotions = PromotionMgr.activeCustomerPromotions.getProductPromotions();
+ var promoIds = collections.map(activePromotions, function (promo) {
+ return promo.ID;
+ });
+
+ session.privacy.promoCache = JSON.stringify({
+ cacheKey: cacheKey,
+ promoIds: promoIds
+ });
+ return promoIds;
+ }
+});
+
+module.exports = promotionCache;
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/accountDashboard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/accountDashboard.isml
new file mode 100644
index 0000000..293d7c9
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/accountDashboard.isml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/account/dashboard.css');
+
+
+
+
${Resource.msg('page.heading.dashboard','account',null)}
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressBook.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressBook.isml
new file mode 100644
index 0000000..33612ac
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressBook.isml
@@ -0,0 +1,74 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addJs('/js/addressBook.js');
+ assets.addCss('/css/account/addressBook.css');
+
+
+
${Resource.msg('label.addressbook','account',null)}
+
+
+
+
+
+
+
+
+
${Resource.msg('msg.no.saved.addresses','address',null)}
+
+
+
+
+
+
+
+
+
+
+
${address.address.firstName} ${address.address.lastName}
+
${address.address.address1}
+
+ ${address.address.address2}
+
+
${address.address.city}, ${address.address.stateCode} ${address.address.postalCode}
+
${address.address.phone}
+
+
+
+
>
+ ×
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressBookCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressBookCard.isml
new file mode 100644
index 0000000..5830985
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressBookCard.isml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
${Resource.msg('label.addressbook.defaultaddress','account',null)}
+
${pdict.account.preferredAddress.address.ID}
+
${pdict.account.preferredAddress.address.firstName} ${pdict.account.preferredAddress.address.lastName}
+
${pdict.account.preferredAddress.address.address1}
+
${pdict.account.preferredAddress.address.city}, ${pdict.account.preferredAddress.address.stateCode} ${pdict.account.preferredAddress.address.postalCode}
+
${pdict.account.preferredAddress.address.phone}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressForm.isml
new file mode 100644
index 0000000..38ddd56
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/addressForm.isml
@@ -0,0 +1,166 @@
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ autocomplete="postal-code">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('button.save','account',null)}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/changePasswordForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/changePasswordForm.isml
new file mode 100644
index 0000000..70efe46
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/changePasswordForm.isml
@@ -0,0 +1,42 @@
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('button.save','account',null)}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/accountEditedEmail.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/accountEditedEmail.isml
new file mode 100644
index 0000000..f9570e3
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/accountEditedEmail.isml
@@ -0,0 +1,11 @@
+
+
+ ${Resource.msg('email.msg.edited.dear', 'account', null)} ${pdict.firstName} ${pdict.lastName},
+
+ ${Resource.msg('email.msg.edited.body1','account',null)}
+
+ ${pdict.url}
+
+ ${Resource.msg('email.msg.edited.body2','account',null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/loginForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/loginForm.isml
new file mode 100644
index 0000000..e1e92f2
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/loginForm.isml
@@ -0,0 +1,48 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/navTabs.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/navTabs.isml
new file mode 100644
index 0000000..e5b7b0c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/navTabs.isml
@@ -0,0 +1,24 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/oauth.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/oauth.isml
new file mode 100644
index 0000000..851ff6d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/oauth.isml
@@ -0,0 +1,16 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/registerForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/registerForm.isml
new file mode 100644
index 0000000..a59d831
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/registerForm.isml
@@ -0,0 +1,146 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/trackOrderForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/trackOrderForm.isml
new file mode 100644
index 0000000..b1e6e95
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/trackOrderForm.isml
@@ -0,0 +1,53 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/trackOrderFormBillingZipCode.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/trackOrderFormBillingZipCode.isml
new file mode 100644
index 0000000..e80fc45
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/components/trackOrderFormBillingZipCode.isml
@@ -0,0 +1,14 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/dashboardProfileCards.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/dashboardProfileCards.isml
new file mode 100644
index 0000000..35cd826
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/dashboardProfileCards.isml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/deleteAddressModal.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/deleteAddressModal.isml
new file mode 100644
index 0000000..fa68a19
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/deleteAddressModal.isml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ ${Resource.msg('msg.delete.address.confirmation.modal','address',null)}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/editAddAddress.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/editAddAddress.isml
new file mode 100644
index 0000000..96dbba0
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/editAddAddress.isml
@@ -0,0 +1,30 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/account/profile.css');
+ assets.addJs('/js/addressBook.js');
+
+
+
${Resource.msg('label.addressbook','account',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/editProfileForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/editProfileForm.isml
new file mode 100644
index 0000000..f8fcc9f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/editProfileForm.isml
@@ -0,0 +1,88 @@
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('link.tracking.consent','account',null)}
+
+
+
+
+
+
+
+ ${Resource.msg('button.save','account',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/header.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/header.isml
new file mode 100644
index 0000000..fdcbd40
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/header.isml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/login.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/login.isml
new file mode 100644
index 0000000..78d03ed
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/login.isml
@@ -0,0 +1,41 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/login.css');
+ assets.addJs('/js/login.js');
+
+
+
+
${Resource.msg('header.hero.image.login', 'login', null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/mobileHeader.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/mobileHeader.isml
new file mode 100644
index 0000000..83e8947
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/mobileHeader.isml
@@ -0,0 +1,26 @@
+
+
+
+
+ ${Resource.msg('link.header.login', 'account', null)}
+
+
+
+
+
+
+ ${pdict.name}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/history.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/history.isml
new file mode 100644
index 0000000..70d47a1
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/history.isml
@@ -0,0 +1,47 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addJs('/js/orderHistory.js');
+ assets.addCss('/css/account/profile.css');
+
+
+
+
${Resource.msg('page.title.myaccount','account',null)}
+
+
+
+
+
+
+
${Resource.msg('label.orderhistory','account',null)}
+
+
+
+
+
+ selected>
+ ${filterValue.displayValue}
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/orderHistoryCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/orderHistoryCard.isml
new file mode 100644
index 0000000..cd56a77
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/orderHistoryCard.isml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.orderhistory.mostrecentorder','account',null)}
+
+
+
+
+
+
+
+ ${Resource.msg('label.orderhistory.ordernumber','account',null)}
+
+
${Resource.msg('label.orderhistory.dateordered','account',null)}
+
${Resource.msg('label.orderhistory.orderstatus','account',null)} ${order.orderStatus}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/orderList.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/orderList.isml
new file mode 100644
index 0000000..1e60407
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/orderList.isml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/shippingTitle.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/shippingTitle.isml
new file mode 100644
index 0000000..cd55406
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/order/shippingTitle.isml
@@ -0,0 +1 @@
+${Resource.msg('label.orderhistory.shippedto','account',null)} ${order.shippedToFirstName} ${order.shippedToLastName}
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/orderDetails.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/orderDetails.isml
new file mode 100644
index 0000000..3ee46f7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/orderDetails.isml
@@ -0,0 +1,26 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/account/orderTrack.css');
+ assets.addCss('/css/account/profile.css');
+
+
+
+
+
${Resource.msg('heading.order.details','order',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password.isml
new file mode 100644
index 0000000..fc90832
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password.isml
@@ -0,0 +1,31 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/account/profile.css');
+ assets.addJs('/js/profile.js');
+
+
+
${Resource.msg('label.profile.password','account',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPassword.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPassword.isml
new file mode 100644
index 0000000..5e4245f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPassword.isml
@@ -0,0 +1,23 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/login.css');
+
+
+
${Resource.msg('label.profile.password','account',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPasswordForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPasswordForm.isml
new file mode 100644
index 0000000..a34723e
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPasswordForm.isml
@@ -0,0 +1,37 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPasswordRedirect.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPasswordRedirect.isml
new file mode 100644
index 0000000..2b89d93
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/newPasswordRedirect.isml
@@ -0,0 +1,8 @@
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordChangedEmail.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordChangedEmail.isml
new file mode 100644
index 0000000..6d563f9
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordChangedEmail.isml
@@ -0,0 +1,13 @@
+
+
+ ${Resource.msg('msg.passwordemail.dear','login',null)} ${pdict.firstName} ${pdict.lastName},
+
+ ${Resource.msg('msg.passwordchangedemail.message1','login',null)}
+
+ ${Resource.msg('msg.passwordchangedemail.message2','login',null)}
+
+ ${pdict.url}
+
+ ${Resource.msg('msg.passwordchangedemail.message3','login',null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordResetEmail.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordResetEmail.isml
new file mode 100644
index 0000000..ecf7e4e
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordResetEmail.isml
@@ -0,0 +1,19 @@
+
+
+ ${Resource.msg('msg.passwordemail.dear','login',null)} ${pdict.firstName} ${pdict.lastName},
+
+ ${Resource.msg('msg.passwordemail.message1','login',null)}
+
+ ${Resource.msg('msg.passwordemail.message2','login',null)}
+
+ ${Resource.msg('msg.passwordemail.message3','login',null)}
+
+ ${pdict.url}
+
+ ${Resource.msg('msg.passwordemail.message4','login',null)}
+
+ ${Resource.msg('msg.passwordemail.message5','login',null)}
+
+ ${Resource.msg('msg.passwordemail.message6','login',null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordResetForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordResetForm.isml
new file mode 100644
index 0000000..40fec52
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/passwordResetForm.isml
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/requestPasswordReset.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/requestPasswordReset.isml
new file mode 100644
index 0000000..daa2d00
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/requestPasswordReset.isml
@@ -0,0 +1,21 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/login.css');
+ assets.addJs('/js/login.js');
+
+
+
${Resource.msg('label.forgotpassword', 'login', null)}
+
+
+
+
+
${Resource.msg('label.forgotpassword.heading','login',null)}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/requestPasswordResetModal.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/requestPasswordResetModal.isml
new file mode 100644
index 0000000..c82e71a
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/password/requestPasswordResetModal.isml
@@ -0,0 +1,14 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/passwordCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/passwordCard.isml
new file mode 100644
index 0000000..e74b966
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/passwordCard.isml
@@ -0,0 +1,12 @@
+
+
+
+
+ ${Resource.msg('label.profile.password','account',null)}
+ ${pdict.account.profile.password}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/addPayment.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/addPayment.isml
new file mode 100644
index 0000000..80c5d73
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/addPayment.isml
@@ -0,0 +1,30 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addJs('/js/paymentInstruments.js');
+ assets.addCss('/css/account/payment.css');
+
+
+
${Resource.msg('page.heading.payments','payment',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/deletePaymentModal.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/deletePaymentModal.isml
new file mode 100644
index 0000000..3b1e204
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/deletePaymentModal.isml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ ${Resource.msg('msg.delete.payment.confirmation.modal','payment',null)}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/payment.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/payment.isml
new file mode 100644
index 0000000..c4d9024
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/payment.isml
@@ -0,0 +1,37 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addJs('/js/paymentInstruments.js');
+ assets.addCss('/css/account/payment.css');
+
+
+
${Resource.msg('page.heading.payments','payment',null)}
+
+
+
+
+
+
+
+
${Resource.msg('msg.no.saved.payments','payment',null)}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/paymentForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/paymentForm.isml
new file mode 100644
index 0000000..60d79ce
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/paymentForm.isml
@@ -0,0 +1,113 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/savedPayment.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/savedPayment.isml
new file mode 100644
index 0000000..47d1fe9
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/savedPayment.isml
@@ -0,0 +1,3 @@
+${Resource.msg('label.payment.credit','account',null)} ${payment.creditCardType}
+${payment.maskedCreditCardNumber}
+${Resource.msg('label.payment.ending','account',null)} ${payment.creditCardExpirationMonth}/${payment.creditCardExpirationYear}
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/savedPayments.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/savedPayments.isml
new file mode 100644
index 0000000..b9b9d11
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/payment/savedPayments.isml
@@ -0,0 +1,31 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/paymentCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/paymentCard.isml
new file mode 100644
index 0000000..81d0d7b
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/paymentCard.isml
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/profile.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/profile.isml
new file mode 100644
index 0000000..280dfe5
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/profile.isml
@@ -0,0 +1,31 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/account/profile.css');
+ assets.addJs('/js/profile.js');
+
+
+
${Resource.msg('label.profile','account',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/profileCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/profileCard.isml
new file mode 100644
index 0000000..08ea5fd
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/account/profileCard.isml
@@ -0,0 +1,29 @@
+
+
+
+
+ ${Resource.msg('label.profile.firstname','account',null)}
+ ${pdict.account.profile.firstName}
+
+
+ ${Resource.msg('label.profile.lastname','account',null)}
+ ${pdict.account.profile.lastName}
+
+
+ ${Resource.msg('label.profile.email','account',null)}
+ ${pdict.account.profile.email}
+
+
+
+ ${Resource.msg('label.profile.phone','account',null)}
+ ${pdict.account.profile.phone}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cart.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cart.isml
new file mode 100644
index 0000000..66f73cd
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cart.isml
@@ -0,0 +1,94 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/cart.css');
+
+
+
+
+
+
+
+
+
+
+ ×
+
+ ${pdict.valid.message}
+
+
+
+
+
+
${Resource.msg('title.cart','cart',null)}
+
+
+
+
+
+
+
+
+
${Resource.msg('info.cart.empty.msg','cart',null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartApproachingDiscount.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartApproachingDiscount.isml
new file mode 100644
index 0000000..c393204
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartApproachingDiscount.isml
@@ -0,0 +1,9 @@
+
+
+
+
+ ${approachingDiscount.discountMsg}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartCouponDisplay.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartCouponDisplay.isml
new file mode 100644
index 0000000..b5e466d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartCouponDisplay.isml
@@ -0,0 +1,37 @@
+
+
+
+
${discount.couponCode} -
+
+ ${Resource.msg('msg.coupon.applied','cart',null)}
+
+ ${Resource.msg('msg.coupon.not.applied','cart',null)}
+
+
+ ×
+
+
+
+
+
+
+ ${discount.lineItemText}
+ ${discount.price}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartPromoCode.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartPromoCode.isml
new file mode 100644
index 0000000..8d2e4b8
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartPromoCode.isml
@@ -0,0 +1,31 @@
+
+
+
${Resource.msg('action.enter.promo.code','cart',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartRemoveCouponModal.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartRemoveCouponModal.isml
new file mode 100644
index 0000000..a59c807
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartRemoveCouponModal.isml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ ${Resource.msg('msg.remove.coupon.confirmation.modal','cart',null)}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartRemoveProductModal.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartRemoveProductModal.isml
new file mode 100644
index 0000000..834b2fb
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartRemoveProductModal.isml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ ${Resource.msg('msg.remove.product.confirmation.modal','cart',null)}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartShippingMethodSelection.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartShippingMethodSelection.isml
new file mode 100644
index 0000000..96e7917
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartShippingMethodSelection.isml
@@ -0,0 +1,16 @@
+
+
+ ${Resource.msg('field.selectshippingmethod','cart',null)}
+
+
+
+ ${option.displayName} (${option.estimatedArrivalTime})
+
+ ${option.displayName} (${option.estimatedArrivalTime})
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartTotals.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartTotals.isml
new file mode 100644
index 0000000..edc09c8
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/cartTotals.isml
@@ -0,0 +1,47 @@
+
+
+
${Resource.msg('label.shipping.cost','cart',null)}
+
+
+
${pdict.totals.totalShippingCost}
+
+
+
+
+hide-shipping-discount">
+
+
${Resource.msg('label.shipping.discount', 'common', null)}
+
+
+
- ${pdict.totals.shippingLevelDiscountTotal.formatted}
+
+
+
+
+
+
+
${Resource.msg('label.sales.tax','cart',null)}
+
+
+
${pdict.totals.totalTax}
+
+
+
+
+hide-order-discount">
+
+
${Resource.msg('label.order.discount', 'common', null)}
+
+
+
- ${pdict.totals.orderLevelDiscountTotal.formatted}
+
+
+
+
+
+ ${Resource.msg('label.estimatedtotal','cart',null)}
+
+
+
${pdict.totals.grandTotal}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/checkoutButtons.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/checkoutButtons.isml
new file mode 100644
index 0000000..2b39e07
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/checkoutButtons.isml
@@ -0,0 +1,7 @@
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartBundleCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartBundleCard.isml
new file mode 100644
index 0000000..3bff901
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartBundleCard.isml
@@ -0,0 +1,37 @@
+
+
+
+
+
${Resource.msg('msg.bundle.includes','cart',null)}
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartEmbeddedBonusProducts.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartEmbeddedBonusProducts.isml
new file mode 100644
index 0000000..62d95be
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartEmbeddedBonusProducts.isml
@@ -0,0 +1,20 @@
+
+
+ ${Resource.msg('text.lineitem.bonus.msg','cart',null)}:
+
+
+
+
+
+
+ ${discountLineItem.msg}
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartNestedBonusProductCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartNestedBonusProductCard.isml
new file mode 100644
index 0000000..a877025
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartNestedBonusProductCard.isml
@@ -0,0 +1,53 @@
+
+
+
${productLineItem.productName}
+
+
+
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+ ${option.displayName}
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCard.isml
new file mode 100644
index 0000000..dbd655c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCard.isml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
${option.displayName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardAvailability.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardAvailability.isml
new file mode 100644
index 0000000..575f024
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardAvailability.isml
@@ -0,0 +1,10 @@
+
+
+ ${message}
+
+
+
+ ${lineItem.availability.inStockDate}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardBundledItems.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardBundledItems.isml
new file mode 100644
index 0000000..5409d27
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardBundledItems.isml
@@ -0,0 +1,26 @@
+
+
+
+ ${bundledLineItem.productName}
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardEdit.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardEdit.isml
new file mode 100644
index 0000000..5b41492
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardEdit.isml
@@ -0,0 +1,9 @@
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardHeader.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardHeader.isml
new file mode 100644
index 0000000..dda481b
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardHeader.isml
@@ -0,0 +1,13 @@
+
+ ${Resource.msg('msg.bonus.product','cart',null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardProductPrice.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardProductPrice.isml
new file mode 100644
index 0000000..18a0e17
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardProductPrice.isml
@@ -0,0 +1,37 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardServer.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardServer.isml
new file mode 100644
index 0000000..e15676e
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/cartProductCardServer.isml
@@ -0,0 +1,15 @@
+
+ This template sets the context for the cartProductCard template
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/uncategorizedCartProductCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/uncategorizedCartProductCard.isml
new file mode 100644
index 0000000..3196f0d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/cart/productCard/uncategorizedCartProductCard.isml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('error.removed.online.catalog', 'cart', null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/addressSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/addressSummary.isml
new file mode 100644
index 0000000..8cb070d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/addressSummary.isml
@@ -0,0 +1,28 @@
+
+
+
+ ${address.firstName}
+
+
+ ${address.lastName}
+
+
+
+
+ ${address.address1}
+
+
+
+ ${address.address2}
+
+
+
+ ${address.city},
+
+
+ ${address.stateCode}
+
+
+ ${address.postalCode}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/addressSelector.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/addressSelector.isml
new file mode 100644
index 0000000..7fe7ca5
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/addressSelector.isml
@@ -0,0 +1,50 @@
+
+
+
+
+ ${Resource.msg('msg.new.address', 'checkout', null)}
+
+
+
+ ${billingAddress.firstName || ''}
+ ${billingAddress.lastName || ''}
+ ${billingAddress.address1 || ''}
+ ${billingAddress.city || ''}${!billingAddress.city || ','}
+ ${billingAddress.stateCode||''}
+ ${billingAddress.postalCode||''}
+
+
+
+
+ ${Resource.msg('msg.account.addresses', 'checkout', null)}
+
+
+ ${address.ID || ''} - ${address.firstName || ''} ${address.lastName || ''} ${address.address1 || ''} ${address.city || ''}${!address.city || ','} ${address.stateCode||''} ${address.postalCode||''}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/addressSelectorShipping.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/addressSelectorShipping.isml
new file mode 100644
index 0000000..684fc4f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/addressSelectorShipping.isml
@@ -0,0 +1,14 @@
+
+ ${Resource.msg('msg.shipping.addresses', 'checkout', null)}
+
+
+ ${aShippingModel.shippingAddress.firstName || ''} ${aShippingModel.shippingAddress.lastName || ''} ${aShippingModel.shippingAddress.address1 || ''} ${aShippingModel.shippingAddress.city || ''}${!aShippingModel.shippingAddress.city || ','} ${aShippingModel.shippingAddress.stateCode||''} ${aShippingModel.shippingAddress.postalCode||''}
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billing.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billing.isml
new file mode 100644
index 0000000..f770809
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billing.isml
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billingAddress.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billingAddress.isml
new file mode 100644
index 0000000..f219131
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billingAddress.isml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
${Resource.msg('field.billing.address.zip.code','address',null)}
+
+ autocomplete="billing postal-code">
+
+
${Resource.msg('example.postalcode','forms',null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billingSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billingSummary.isml
new file mode 100644
index 0000000..4c02c8a
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/billingSummary.isml
@@ -0,0 +1,23 @@
+${Resource.msg('label.order.billing.address', 'confirmation', null)}
+
+
+
+
+
+
+
+
+ ${pdict.order.orderEmail}
+
+
+ ${pdict.order.billing.billingAddress.address.phone}
+
+
+
+
+
+
+
+ ${Resource.msg('label.order.payment.info', 'confirmation', null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/creditCardForm.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/creditCardForm.isml
new file mode 100644
index 0000000..a2a3ed3
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/creditCardForm.isml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${creditFields.saveCard.label}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions.isml
new file mode 100644
index 0000000..a114dc4
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions.isml
@@ -0,0 +1,13 @@
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardContent.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardContent.isml
new file mode 100644
index 0000000..5a1098b
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardContent.isml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('button.add.payment', 'checkout', null)}
+
+
+
+
+
+
+
+ ${Resource.msg('button.back.to.stored.payments', 'checkout', null)}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardSummary.isml
new file mode 100644
index 0000000..3307761
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardSummary.isml
@@ -0,0 +1,10 @@
+
+ ${Resource.msg('msg.payment.type.credit', 'confirmation', null)}
+ ${payment.type}
+
+
+ ${payment.maskedCreditCardNumber}
+
+
+ ${Resource.msg('msg.card.type.ending', 'confirmation', null)} ${payment.expirationMonth}/${payment.expirationYear}
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardTab.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardTab.isml
new file mode 100644
index 0000000..bb7a757
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/creditCardTab.isml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsContent.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsContent.isml
new file mode 100644
index 0000000..2926b70
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsContent.isml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsSummary.isml
new file mode 100644
index 0000000..c1dddee
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsSummary.isml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsTabs.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsTabs.isml
new file mode 100644
index 0000000..0c55cc8
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/paymentOptions/paymentOptionsTabs.isml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/storedPaymentInstruments.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/storedPaymentInstruments.isml
new file mode 100644
index 0000000..2e31c2d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/billing/storedPaymentInstruments.isml
@@ -0,0 +1,42 @@
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/cart/miniCart.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/cart/miniCart.isml
new file mode 100644
index 0000000..4f3639c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/cart/miniCart.isml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
${Resource.msg('title.your.shopping.cart','cart',null)}
+
+
+
+
+
+
+
+ ×
+
+ ${pdict.valid.message}
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/checkout.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/checkout.isml
new file mode 100644
index 0000000..a3ac669
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/checkout.isml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addJs('/js/checkout.js');
+ assets.addCss('/css/checkout/checkout.css');
+
+
+
+
+
+
+
+ ${Resource.msg('title.checkout','checkout',null)}
+
+ 1}">multi-ship" data-customer-type="${pdict.customer.registeredUser ? 'registered' : 'guest'}" data-checkout-stage="${pdict.currentStage}" data-checkout-get-url="${URLUtils.https('CheckoutServices-Get')}">
+
+
+
+
+
+
+
+ ${Resource.msgf('label.number.items.in.cart','cart', null, pdict.order.items.totalQuantity)}
+ ${pdict.order.totals.subTotal}
+
+
+
+
+
+
+
+
+
+
+
+
+
We only allow edits for non-registered customers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/checkoutLogin.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/checkoutLogin.isml
new file mode 100644
index 0000000..87debc1
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/checkoutLogin.isml
@@ -0,0 +1,63 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/checkout/checkoutLogin.css');
+ assets.addJs('/js/login.js');
+
+
+
+
+
+
+
+ ${Resource.msg('title.checkout', 'checkout', null)}
+
+
+
+
+ ${Resource.msgf('label.number.items.in.cart','cart', null, pdict.details.totalQuantity)}
+ ${pdict.details.subTotal}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/accountRegisteredEmail.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/accountRegisteredEmail.isml
new file mode 100644
index 0000000..92a2ac0
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/accountRegisteredEmail.isml
@@ -0,0 +1,11 @@
+
+
+ ${Resource.msg('email.msg.new.registration.dear', 'registration', null)} ${pdict.firstName} ${pdict.lastName},
+
+ ${Resource.msg('email.msg.new.registration.body1','registration',null)}
+
+ ${pdict.url}
+
+ ${Resource.msg('email.msg.new.registration.body2','registration',null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmation.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmation.isml
new file mode 100644
index 0000000..344208f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmation.isml
@@ -0,0 +1,40 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/checkout/checkout.css');
+ assets.addJs('/js/checkoutRegistration.js');
+
+
+
+
+
+
+
+
${Resource.msg('title.thank.you.page','confirmation',null)}
+
+ 1}">multi-ship">
+
+
+
${Resource.msg('msg.placed.order.thank.you','confirmation',null)}
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationCreateAccount.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationCreateAccount.isml
new file mode 100644
index 0000000..d430d37
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationCreateAccount.isml
@@ -0,0 +1,51 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationCreateAccountExtension.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationCreateAccountExtension.isml
new file mode 100644
index 0000000..48c1546
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationCreateAccountExtension.isml
@@ -0,0 +1,3 @@
+
+ This template is intentionally left blank. It is created to allow plugin cartridge to override for adding the additional fields
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationDetails.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationDetails.isml
new file mode 100644
index 0000000..af0f6de
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationDetails.isml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ ${Resource.msg('label.order.number', 'confirmation', null)}
+ ${pdict.order.orderNumber}
+
+ ${Resource.msg('label.order.date', 'confirmation', null)}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationEmail.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationEmail.isml
new file mode 100644
index 0000000..11f12a5
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationEmail.isml
@@ -0,0 +1,172 @@
+
+
+
${Resource.msg('msg.placed.order.thank.you', 'confirmation', null)}
+
+
+ ${Resource.msg('label.order.number', 'confirmation', null)}
+
+ ${pdict.order.orderNumber}
+
+
+ ${Resource.msg('label.order.date', 'confirmation', null)}
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.order.shipping.method', 'confirmation', null)}
+
+ ${shippingModel.selectedShippingMethod.displayName}
+
+ (${shippingModel.selectedShippingMethod.estimatedArrivalTime})
+
+
+
+
+
+
+ ${Resource.msg('heading.gift.message', 'checkout', null)}
+
+ ${shippingModel.giftMessage}
+
+
+
+
+ ${Resource.msg('label.order.products', 'confirmation', null)}:
+
+
+ ${Resource.msg('label.order.product.name', 'confirmation', null)}: ${lineItem.productName}
+
+
+
+
+ ${Resource.msg('label.order.bundled.item.name', 'confirmation', null)}: ${bundledLineItem.productName}
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
+ ${Resource.msg('label.each.item.price','cart',null)} -
+ ${lineItem.priceTotal.price}
+
+
+ ${Resource.msg('field.selectquantity','cart',null)}
+ -
+
+
+
+ ${Resource.msg('label.total.price','cart',null)}
+ - ${lineItem.priceTotal.price}
+
+
+
+
+
+ ${pdict.order.totals.totalShippingCost}
+
+ ${Resource.msg('label.order.billing.address', 'confirmation', null)}
+
+
+
+
+
+
+
+
+
${Resource.msg('label.order.payment.info', 'confirmation', null)}
+
+
+
+
+
+
+
+ ${Resource.msgf('label.number.items.in.cart','cart', null, pdict.order.items.totalQuantity)}
+
+ ${pdict.order.totals.subTotal}
+
+
+
+
+ ${Resource.msg('label.order.products', 'confirmation', null)}:
+
+
+ ${Resource.msg('label.order.product.name', 'confirmation', null)}: ${lineItem.productName}
+
+
+
+
+ ${Resource.msg('label.order.bundled.item.name', 'confirmation', null)}: ${bundledLineItem.productName}
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
+
+
+
+ ${Resource.msg('text.lineitem.bonus.msg','cart',null)}
+
+ ${bonusProduct.productName}
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+ ${Resource.msg('label.each.item.price','cart',null)} -
+ ${bonusProduct.priceTotal.price}
+
+
+
+ ${Resource.msg('field.selectquantity','cart',null)}
+ -
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.each.item.price','cart',null)} -
+ ${lineItem.priceTotal.price}
+
+
+ ${Resource.msg('field.selectquantity','cart',null)}
+ -
+
+
+
+ ${Resource.msg('label.total.price','cart',null)}
+ - ${lineItem.priceTotal.price}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationOrderTotal.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationOrderTotal.isml
new file mode 100644
index 0000000..afb7c79
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationOrderTotal.isml
@@ -0,0 +1,31 @@
+${Resource.msg('label.order.totals', 'confirmation', null)}:
+
+${Resource.msg('label.order.subtotal','confirmation', null)}:
+${pdict.order.totals.subTotal}
+
+
+
+ ${Resource.msg('label.order.discount', 'common', null)}:
+ - ${pdict.order.totals.orderLevelDiscountTotal.formatted}
+
+
+
+
+${Resource.msg('label.order.shipping.cost','confirmation', null)}:
+${pdict.order.totals.totalShippingCost}
+
+
+
+ ${Resource.msg('label.shipping.discount', 'common', null)}:
+ - ${pdict.order.totals.shippingLevelDiscountTotal.formatted}
+
+
+
+
+${Resource.msg('label.order.sales.tax','confirmation', null)}:
+${pdict.order.totals.totalTax}
+
+
+
+${Resource.msg('label.order.grand.total','confirmation', null)}:
+${pdict.order.totals.grandTotal}
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationPaymentInfo.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationPaymentInfo.isml
new file mode 100644
index 0000000..20ac908
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/confirmationPaymentInfo.isml
@@ -0,0 +1,17 @@
+
+ This template is created to render payment information in the order confirmation email.
+
+
+
+
+ ${Resource.msg('msg.payment.type.credit', 'confirmation', null)}
+ ${payment.type}
+
+
+ ${payment.maskedCreditCardNumber}
+
+
+ ${Resource.msg('msg.card.type.ending', 'confirmation', null)} ${payment.expirationMonth}/${payment.expirationYear}
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/shippingTitle.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/shippingTitle.isml
new file mode 100644
index 0000000..7d9a078
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/confirmation/shippingTitle.isml
@@ -0,0 +1 @@
+${Resource.msg('label.order.shipping.address', 'confirmation', null)}
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customer.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customer.isml
new file mode 100644
index 0000000..b5c7b42
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customer.isml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerCard.isml
new file mode 100644
index 0000000..732b4a1
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerCard.isml
@@ -0,0 +1,69 @@
+
+
novalidate>
+
+
+
+
+ ${Resource.msg('button.guest.customer.continue', 'checkout', null)}
+
+
+ ${Resource.msg('msg.guest.customer.exists', 'checkout', null)} ${Resource.msg('action.customer.login', 'checkout', null)}
+
+
+
+
novalidate>
+
+
+
+
+
+
+ ${Resource.msg('button.customer.login', 'checkout', null)}
+
+
+
+ ${Resource.msg('button.customer.login.cancel', 'checkout', null)}
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerOAuthCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerOAuthCard.isml
new file mode 100644
index 0000000..f028898
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerOAuthCard.isml
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerSummary.isml
new file mode 100644
index 0000000..254b970
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/customer/customerSummary.isml
@@ -0,0 +1,8 @@
+
+ ${Resource.msg('label.customer.data', 'confirmation', null)}
+
+
+
+ ${(pdict.customer.profile && pdict.customer.profile.email) ? pdict.customer.profile.email : pdict.order.orderEmail}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/orderProductSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/orderProductSummary.isml
new file mode 100644
index 0000000..762f166
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/orderProductSummary.isml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ ${Resource.msgf('label.number.items.in.cart','cart', null, pdict.order.items.totalQuantity)}
+
+
+ ${pdict.order.totals.subTotal}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/orderTotalSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/orderTotalSummary.isml
new file mode 100644
index 0000000..9293193
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/orderTotalSummary.isml
@@ -0,0 +1,60 @@
+
+
+
+
${Resource.msg('label.order.subtotal','confirmation', null)}
+
+
+
${pdict.order.totals.subTotal}
+
+
+
+
+hide-order-discount">
+
+
${Resource.msg('label.order.discount', 'common', null)}
+
+
+
- ${pdict.order.totals.orderLevelDiscountTotal.formatted}
+
+
+
+
+
+
+
${Resource.msg('label.order.shipping.cost','confirmation', null)}
+
+
+
${pdict.order.totals.totalShippingCost}
+
+
+
+
+hide-shipping-discount">
+
+
${Resource.msg('label.shipping.discount', 'common', null)}
+
+
+
- ${pdict.order.totals.shippingLevelDiscountTotal.formatted}
+
+
+
+
+
+
+
${Resource.msg('label.order.sales.tax','confirmation', null)}
+
+
+
${pdict.order.totals.totalTax}
+
+
+
+
+
+
+
${Resource.msg('label.order.grand.total','confirmation', null)}
+
+
+
${pdict.order.totals.grandTotal}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/bonusProductCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/bonusProductCard.isml
new file mode 100644
index 0000000..9b1c32d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/bonusProductCard.isml
@@ -0,0 +1,90 @@
+
+
+
+
+ ${Resource.msg('msg.bundle.includes','cart',null)}
+
+
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.each.item.price','cart',null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.total.price','cart',null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('text.lineitem.bonus.msg', 'cart', null)}:
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/embeddedBonusProduct.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/embeddedBonusProduct.isml
new file mode 100644
index 0000000..1ff9ccc
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/embeddedBonusProduct.isml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+ ${option.displayName}
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.each.item.price','cart',null)}
+
+
+
+
+ ${nestedLineItem.bonusUnitPrice}
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('field.selectquantity','cart',null)}
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.total.price','cart',null)}
+
+
+
+
${nestedLineItem.priceTotal.price}
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCard.isml
new file mode 100644
index 0000000..c5242a0
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCard.isml
@@ -0,0 +1,84 @@
+
+
+
+
+ ${Resource.msg('msg.bundle.includes','cart',null)}
+
+
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+ ${option.displayName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.each.item.price','cart',null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.total.price','cart',null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductName.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductName.isml
new file mode 100644
index 0000000..889f9f3
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductName.isml
@@ -0,0 +1,3 @@
+
+ ${lineItem.productName}
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductNameAndRemove.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductNameAndRemove.isml
new file mode 100644
index 0000000..b3aaa34
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductNameAndRemove.isml
@@ -0,0 +1,15 @@
+
+ ${lineItem.productName}
+
+
+
+
+ ×
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductPromotions.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductPromotions.isml
new file mode 100644
index 0000000..cf316ce
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductPromotions.isml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductRenderedPromotions.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductRenderedPromotions.isml
new file mode 100644
index 0000000..04304b9
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductRenderedPromotions.isml
@@ -0,0 +1,6 @@
+
+ This template sets the context for the productCardProductRenderedPromotions template
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductRenderedTotalPrice.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductRenderedTotalPrice.isml
new file mode 100644
index 0000000..1802926
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductRenderedTotalPrice.isml
@@ -0,0 +1,6 @@
+
+ This template sets the context for the productCardProductRenderedTotalPrice template
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductTotalPrice.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductTotalPrice.isml
new file mode 100644
index 0000000..c759ecf
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardProductTotalPrice.isml
@@ -0,0 +1,6 @@
+
+ ${lineItem.priceTotal.nonAdjustedPrice}
+
+${lineItem.priceTotal.price}
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardQuantityDisplay.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardQuantityDisplay.isml
new file mode 100644
index 0000000..8dd89dc
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardQuantityDisplay.isml
@@ -0,0 +1,6 @@
+
+
+ ${Resource.msg('field.selectquantity','cart',null)}
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardQuantitySelector.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardQuantitySelector.isml
new file mode 100644
index 0000000..17e84cb
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productCardQuantitySelector.isml
@@ -0,0 +1,21 @@
+
+ ${Resource.msg('field.selectquantity','cart',null)}
+
+
+
+ ${status.index}
+
+ ${status.index}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productShippingCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productShippingCard.isml
new file mode 100644
index 0000000..651551c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/productShippingCard.isml
@@ -0,0 +1,30 @@
+
+
+
+
+ ${Resource.msg('msg.bundle.includes', 'cart', null)}
+
+
+
+
+
+
+
+
+
+ ${attribute.displayName}: ${attribute.displayValue}
+
+
+
+
+ ${option.displayName}
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/uncategorizedProductCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/uncategorizedProductCard.isml
new file mode 100644
index 0000000..c70db88
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/productCard/uncategorizedProductCard.isml
@@ -0,0 +1,34 @@
+
+
+
+
+
+ ${Resource.msg('msg.bundle.includes','cart',null)}
+
+
+
+
+
+
+
+
+ ${Resource.msg('error.removed.online.catalog', 'cart', null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/giftMessageSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/giftMessageSummary.isml
new file mode 100644
index 0000000..6aace2c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/giftMessageSummary.isml
@@ -0,0 +1,6 @@
+
+
${Resource.msg('heading.gift.message', 'checkout', null)}
+
+ ${shippingModel.giftMessage ? shippingModel.giftMessage : ''}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/isGift.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/isGift.isml
new file mode 100644
index 0000000..28a9de7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/isGift.isml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ ${Resource.msg('label.input.giftmessage', 'forms', null)}
+
+ >${shippingModel.isGift && shippingModel.giftMessage ? shippingModel.giftMessage: '' }
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/multiShipCheckbox.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/multiShipCheckbox.isml
new file mode 100644
index 0000000..24a00df
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/multiShipCheckbox.isml
@@ -0,0 +1,18 @@
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/multiShippingButtons.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/multiShippingButtons.isml
new file mode 100644
index 0000000..80561e1
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/multiShippingButtons.isml
@@ -0,0 +1,24 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/selectedShippingMethod.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/selectedShippingMethod.isml
new file mode 100644
index 0000000..e6699ac
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/selectedShippingMethod.isml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentCard.isml
new file mode 100644
index 0000000..b5773d3
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentCard.isml
@@ -0,0 +1,44 @@
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('heading.shipping.method', 'checkout', null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentSelector.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentSelector.isml
new file mode 100644
index 0000000..90d1a51
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentSelector.isml
@@ -0,0 +1,19 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentSelectorOptions.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentSelectorOptions.isml
new file mode 100644
index 0000000..5d2913d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipmentSelectorOptions.isml
@@ -0,0 +1,59 @@
+
+ ${Resource.msg('msg.add.new.address', 'checkout', null)}
+
+
+ ${Resource.msg('msg.shipping.addresses', 'checkout', null)}
+
+
+
+
+ ${aShippingModel.shippingAddress.firstName || ''}
+ ${aShippingModel.shippingAddress.lastName || ''}
+ ${aShippingModel.shippingAddress.address1 || ''}
+ ${aShippingModel.shippingAddress.address2 || ''}
+ ${aShippingModel.shippingAddress.city || ''}${!aShippingModel.shippingAddress.city || ','}
+ ${aShippingModel.shippingAddress.stateCode||''}
+ ${aShippingModel.shippingAddress.postalCode||''}
+ ${!aShippingModel.selectedShippingMethod.displayName||'-'}
+ ${aShippingModel.selectedShippingMethod.displayName||''}
+
+ ${Resource.msg('msg.new.address', 'checkout', null)}
+
+
+
+
+
+
+ ${Resource.msg('msg.account.addresses', 'checkout', null)}
+
+
+ ${address.ID || ''} - ${address.firstName || ''} ${address.lastName || ''} ${address.address1 || ''} ${address.address2 || ''} ${address.city || ''}${!address.city || ','} ${address.stateCode||''} ${address.postalCode||''}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipping.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipping.isml
new file mode 100644
index 0000000..f5ec21b
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shipping.isml
@@ -0,0 +1,38 @@
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingAddress.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingAddress.isml
new file mode 100644
index 0000000..c62c9f7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingAddress.isml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('field.shipping.address.zip.code','address',null)}
+
+
+ autocomplete="shipping postal-code">
+
+
${Resource.msg('example.postalcode','forms',null)}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingAddressTemplate.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingAddressTemplate.isml
new file mode 100644
index 0000000..0bae38e
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingAddressTemplate.isml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
${Resource.msg('heading.shipping.method', 'checkout', null)}
+
+
+
+
${Resource.msg('heading.gift.message', 'checkout', null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingCard.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingCard.isml
new file mode 100644
index 0000000..1ba9981
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingCard.isml
@@ -0,0 +1,29 @@
+
+ ${Resource.msg('heading.shipping.to', 'checkout', null)}
+
+
+
+
+ ${pdict.order.resources.shippingMethod}
+
+
+ - ${shippingModel.productLineItems.items.length.toFixed(0)} ${pdict.order.resources.items}
+
+
+
+
+
+ ${shippingModel.selectedShippingMethod.displayName}
+
+ (${shippingModel.selectedShippingMethod.estimatedArrivalTime})
+
+
+
+
+ ${shippingModel.selectedShippingMethod.shippingCost}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingCardShippingAddress.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingCardShippingAddress.isml
new file mode 100644
index 0000000..37dd448
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingCardShippingAddress.isml
@@ -0,0 +1,18 @@
+
+
+ ${shippingModel.shippingAddress.firstName} ${shippingModel.shippingAddress.lastName}
+ ${shippingModel.shippingAddress.address1}
+ ${shippingModel.shippingAddress.address2}
+ ${shippingModel.shippingAddress.city}, ${shippingModel.shippingAddress.stateCode} ${shippingModel.shippingAddress.postalCode}
+ ${shippingModel.shippingAddress.phone}
+
+ ${Resource.msg('heading.address.incomplete', 'checkout', null)}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingMethod.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingMethod.isml
new file mode 100644
index 0000000..ede1ce5
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingMethod.isml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+ ${shippingMethod.displayName}
+
+ (${shippingMethod.estimatedArrivalTime})
+
+
+
+
+ ${shippingMethod.shippingCost}
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingSummary.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingSummary.isml
new file mode 100644
index 0000000..3a8bd71
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingSummary.isml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${shippingModel.shippingAddress && shippingModel.shippingAddress.phone ? shippingModel.shippingAddress.phone : ''}
+
+
+
+
+
${Resource.msg('label.order.shipping.method', 'confirmation', null)}
+
+
+
+ ${shippingModel.selectedShippingMethod.displayName}
+
+
+ ( ${shippingModel.selectedShippingMethod.estimatedArrivalTime} )
+
+
+
+
+
+
+ ${shippingModel.selectedShippingMethod.shippingCost}
+
+
+
+
+
+
+
+
+ ${Resource.msg('order.summery.section.text', 'order', null)}
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingTitle.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingTitle.isml
new file mode 100644
index 0000000..9cb3cc2
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/checkout/shipping/shippingTitle.isml
@@ -0,0 +1 @@
+${Resource.msg('label.order.shipping.address', 'confirmation', null)}
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/consent.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/consent.isml
new file mode 100644
index 0000000..15f03ee
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/consent.isml
@@ -0,0 +1,11 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/htmlHead.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/htmlHead.isml
new file mode 100644
index 0000000..dc8c386
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/htmlHead.isml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+The page meta data will be replaced by rule based content if it's available
+
+ ${pdict.CurrentPageMetaData.title} | ${Resource.msg('global.site.name', 'version', null)} | ${Resource.msg('global.version.number', 'version', null)}
+
+
+
+
+
+
+
+Additional Rule based page meta tags
+
+
+ " content="">
+
+ " content="">
+
+
+
+
+
+
+
+
+
+
+
+ integrity="${style.integrity}" crossorigin="anonymous" />
+
+
+
+ " />
+
+
+
+ hook for Marketing Cloud connector & other integration which need to inject
+ logic or markup into the page header
+ IMPORTANT: Note that this hook will be called to cached as well as uncached pages
+ Refrain from calling processing intensive logic in here
+ do not include shopper specific information.
+
+${dw.system.HookMgr.callHook('app.template.htmlHead', 'htmlHead', pdict) || ''}
+
+ Module to overlay skinning css
+ IMPORTANT: Note that this module will be called to cached as well as uncached pages
+ Refrain from calling processing intensive logic in here
+ do not include shopper specific information.
+ This hook will be executed with encoding disabled. Please sanitize output in the hook.
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/checkout.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/checkout.isml
new file mode 100644
index 0000000..e096609
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/checkout.isml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${dw.system.HookMgr.callHook('app.template.beforeHeader', 'beforeHeader', pdict) || ''}
+
+
+
+
+
+ hook for Marketing Cloud connector & other integration which need to inject
+ logic at the page end
+ IMPORTANT: Note that this hook will be called to cached as well as uncached pages
+ which means you need to put privacy information into another remote include
+
+ ${dw.system.HookMgr.callHook('app.template.afterFooter', 'afterFooter', pdict) || ''}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/page.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/page.isml
new file mode 100644
index 0000000..73b6adb
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/page.isml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${dw.system.HookMgr.callHook('app.template.beforeHeader', 'beforeHeader', pdict) || ''}
+
+
+
+
+
+
+
+ hook for Marketing Cloud connector & other integration which need to inject
+ logic at the page end
+ IMPORTANT: Note that this hook will be called to cached as well as uncached pages
+ which means you need to put privacy information into another remote include
+
+ ${dw.system.HookMgr.callHook('app.template.afterFooter', 'afterFooter', pdict) || ''}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/pdStorePage.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/pdStorePage.isml
new file mode 100644
index 0000000..51aa937
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/layout/pdStorePage.isml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/experience/components/commerceAssets/campaignBanner.css');
+ assets.addJs('/js/campaignBanner.js');
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${dw.system.HookMgr.callHook('app.template.beforeHeader', 'beforeHeader', pdict) || ''}
+
+
+
+
+
+
+
+ hook for Marketing Cloud connector & other integration which need to inject
+ logic at the page end
+ IMPORTANT: Note that this hook will be called to cached as well as uncached pages
+ which means you need to put privacy information into another remote include
+
+ ${dw.system.HookMgr.callHook('app.template.afterFooter', 'afterFooter', pdict) || ''}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/scripts.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/scripts.isml
new file mode 100644
index 0000000..2a735c3
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/common/scripts.isml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/breadcrumbs/pageBreadcrumbs.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/breadcrumbs/pageBreadcrumbs.isml
new file mode 100644
index 0000000..644f0be
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/breadcrumbs/pageBreadcrumbs.isml
@@ -0,0 +1,19 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/contentAsset.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/contentAsset.isml
new file mode 100644
index 0000000..c9422b6
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/contentAsset.isml
@@ -0,0 +1 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/contentAssetInc.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/contentAssetInc.isml
new file mode 100644
index 0000000..a583857
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/contentAssetInc.isml
@@ -0,0 +1,8 @@
+
+ don't remove or rename DIV and Marker, necessary for mark and editing, no css attributes defined
+
+
+
+
+ ${Resource.msg('global.nocontent','common',null)}
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/offlineContent.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/offlineContent.isml
new file mode 100644
index 0000000..f783eb5
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/content/offlineContent.isml
@@ -0,0 +1 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/deleteButton.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/deleteButton.isml
new file mode 100644
index 0000000..faf54bd
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/deleteButton.isml
@@ -0,0 +1,8 @@
+
+ ×
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/footer/pageFooter.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/footer/pageFooter.isml
new file mode 100644
index 0000000..85245ae
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/footer/pageFooter.isml
@@ -0,0 +1,33 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/countrySelector.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/countrySelector.isml
new file mode 100644
index 0000000..ac582c7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/countrySelector.isml
@@ -0,0 +1,16 @@
+
+
+
+
+ ${pdict.localeModel.locale.displayName}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/menu.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/menu.isml
new file mode 100644
index 0000000..dea8424
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/menu.isml
@@ -0,0 +1,37 @@
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/menuItem.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/menuItem.isml
new file mode 100644
index 0000000..29ee3c6
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/menuItem.isml
@@ -0,0 +1,15 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/miniCart.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/miniCart.isml
new file mode 100644
index 0000000..5bf0ab3
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/miniCart.isml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/mobileCountrySelector.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/mobileCountrySelector.isml
new file mode 100644
index 0000000..bf7c81f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/mobileCountrySelector.isml
@@ -0,0 +1,16 @@
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pageHeader.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pageHeader.isml
new file mode 100644
index 0000000..9925e41
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pageHeader.isml
@@ -0,0 +1,61 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pageHeaderNomenu.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pageHeaderNomenu.isml
new file mode 100644
index 0000000..0c3d8ce
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pageHeaderNomenu.isml
@@ -0,0 +1,30 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pdStorePageHeader.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pdStorePageHeader.isml
new file mode 100644
index 0000000..8bcbb71
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/pdStorePageHeader.isml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/search.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/search.isml
new file mode 100644
index 0000000..d396b0f
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/search.isml
@@ -0,0 +1,26 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/skipNav.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/skipNav.isml
new file mode 100644
index 0000000..e429bb4
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/header/skipNav.isml
@@ -0,0 +1,2 @@
+${Resource.msg('global.skiptomain', 'common', null)}
+${Resource.msg('global.skiptofooter', 'common', null)}
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/modules.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/modules.isml
new file mode 100644
index 0000000..f1a839d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/modules.isml
@@ -0,0 +1,4 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/products.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/products.isml
new file mode 100644
index 0000000..581bd1d
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/products.isml
@@ -0,0 +1,9 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/schema.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/schema.isml
new file mode 100644
index 0000000..fb0a189
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/schema.isml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/skin.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/skin.isml
new file mode 100644
index 0000000..09f9262
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/components/skin.isml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/contactUs/contactUs.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/contactUs/contactUs.isml
new file mode 100644
index 0000000..264164c
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/contactUs/contactUs.isml
@@ -0,0 +1,108 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addJs('/js/contactUs.js');
+ assets.addCss('/css/contactUs.css');
+
+
+
+
${Resource.msg('title.contact.us.page', 'contactUs', null)}
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/content/contentAsset.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/content/contentAsset.isml
new file mode 100644
index 0000000..7066e30
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/content/contentAsset.isml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/csrfFail.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/csrfFail.isml
new file mode 100644
index 0000000..f6d1097
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/csrfFail.isml
@@ -0,0 +1,24 @@
+
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/csrf.css');
+
+
+
+
${Resource.msg('heading.error.general','error',null)}
+
+
+
+
+
${Resource.msg('error.csrf.token.mismatch', 'common', null)}
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/error.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/error.isml
new file mode 100644
index 0000000..dfc0ccf
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/error.isml
@@ -0,0 +1,25 @@
+
+
+
+
${Resource.msg('heading.error.general','error',null)}
+
+
+
+
${pdict.message}
+
+
+
+
+ in controller ${pdict.error.controllerName}
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/error/notFound.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/error/notFound.isml
new file mode 100644
index 0000000..7b7f737
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/error/notFound.isml
@@ -0,0 +1,17 @@
+
+
+
+
${Resource.msg('heading.error.general', 'error', null)}
+
+
+
+
${Resource.msg('message.page.not.found', 'error', null)}
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/campaignBanner.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/campaignBanner.isml
new file mode 100644
index 0000000..fc0a4db
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/campaignBanner.isml
@@ -0,0 +1,15 @@
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/category.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/category.isml
new file mode 100644
index 0000000..228ce6b
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/category.isml
@@ -0,0 +1,36 @@
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/experience/components/commerceAssets/category.css');
+
+
+
+ style="background-image: url('${pdict.image.url}');
+ background-position-x: ${pdict.image.focalPointX};
+ background-position-y: ${pdict.image.focalPointY};"
+
+ >
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/categoryBanner.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/categoryBanner.isml
new file mode 100644
index 0000000..f5c3279
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/categoryBanner.isml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/editorialRichText.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/editorialRichText.isml
new file mode 100644
index 0000000..bc121c7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/editorialRichText.isml
@@ -0,0 +1,11 @@
+
+ var assets = require('*/cartridge/scripts/assets.js');
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/imageAndText.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/imageAndText.isml
new file mode 100644
index 0000000..d1f9bb6
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/imageAndText.isml
@@ -0,0 +1,43 @@
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/experience/components/commerceAssets/imageAndTextCommon.css');
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/mainBanner.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/mainBanner.isml
new file mode 100644
index 0000000..d0b28eb
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/mainBanner.isml
@@ -0,0 +1,39 @@
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/experience/components/commerceAssets/imageAndTextCommon.css');
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/photoTile.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/photoTile.isml
new file mode 100644
index 0000000..eb8b284
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/photoTile.isml
@@ -0,0 +1,23 @@
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/experience/components/commerceAssets/photoTile.css');
+
+
+
+
+
+
+
+
+ alt="${pdict.image.alt}" title="${pdict.image.alt}"
+ />
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/popularCategory.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/popularCategory.isml
new file mode 100644
index 0000000..2e4d230
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/popularCategory.isml
@@ -0,0 +1,54 @@
+
+ var assets = require('*/cartridge/scripts/assets.js');
+ assets.addCss('/css/experience/components/commerceAssets/popularCategory.css');
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/default.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/default.isml
new file mode 100644
index 0000000..46c6674
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/default.isml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+ ${Resource.msg('label.price.reduced.from', 'pricing', null)}
+
+
+ ${price.formatted}
+
+
+ ${Resource.msg('label.price.to', 'pricing', null)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${price.formatted}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/main.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/main.isml
new file mode 100644
index 0000000..4c03539
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/main.isml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/range.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/range.isml
new file mode 100644
index 0000000..2d700b5
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/pricing/range.isml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ -
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTile.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTile.isml
new file mode 100644
index 0000000..c9d9bd6
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTile.isml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileRatings.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileRatings.isml
new file mode 100644
index 0000000..a396f3e
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileRatings.isml
@@ -0,0 +1,20 @@
+
+
+ ${Resource.msgf('label.product.ratings', 'common', null, product.rating)}
+
+
+ = star}">
+ fa-star
+
+ fa-star-half-o
+
+ fa-star-o
+
+ " aria-hidden="true">
+
+
+
+ ${product.rating}
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileSwatch.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileSwatch.isml
new file mode 100644
index 0000000..cf43cac
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileSwatch.isml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+ ${swatch.images.swatch[0].alt}
+
+
+
+
+
+ 5+
+
+
+
+
+
+
+
+
+ 3+
+
+
+
+
+
diff --git a/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileWrapper.isml b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileWrapper.isml
new file mode 100644
index 0000000..7e14ac7
--- /dev/null
+++ b/storefront-reference-architecture/cartridges/app_storefront_base/cartridge/templates/default/experience/components/commerce_assets/product/productTileWrapper.isml
@@ -0,0 +1,9 @@
+
+
+
+
"
+ class="carousel-item ${ pdict.index == 0 ? 'active': ''} ${pdict.display.xs} ${pdict.display.sm} ${pdict.display.md}">
+
+