diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10bc6b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + +# DW CLI +/**/dw*.json + +#VS Code +*.code-workspace +.vscode diff --git a/link/.gitignore b/link/.gitignore new file mode 100644 index 0000000..22e6eaf --- /dev/null +++ b/link/.gitignore @@ -0,0 +1,71 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + +# Metadata +/metadata/metadata/ +/metadata/*.zip + +# DW CLI +/**/dw*.json + +# SFRA + +/cartridges/int_cybersource_sfra/cartridge/static/*/css/ +/cartridges/int_cybersource_sfra/cartridge/static/*/js/ + +/cartridges/intermix_core/cartridge/static/*/js/ +/cartridges/intermix_core/cartridge/static/*/css/ +# Yeoman +/.yo-repository/ + +#VS Code +*.code-workspace +.vscode + diff --git a/link/cartridges/int_signifyd/cartridge/scripts/service/signifyd.js b/link/cartridges/int_signifyd/cartridge/scripts/service/signifyd.js index 8906131..699a6a7 100644 --- a/link/cartridges/int_signifyd/cartridge/scripts/service/signifyd.js +++ b/link/cartridges/int_signifyd/cartridge/scripts/service/signifyd.js @@ -359,6 +359,7 @@ function process(body) { if (body.checkpointAction) { if (body.checkpointAction.toUpperCase() === 'ACCEPT') { order.custom.SignifydPolicy = 'accept'; + order.setStatus(order.EXPORT_STATUS_READY); } else if (body.checkpointAction.toUpperCase() === 'REJECT') { order.custom.SignifydPolicy = 'reject'; } else { @@ -460,6 +461,7 @@ function setOrderSessionId(order, orderSessionId) { var SignifydCreateCasePolicy = dw.system.Site.getCurrent().getCustomPreferenceValue('SignifydCreateCasePolicy').value; var SignifydDecisionRequest = dw.system.Site.getCurrent().getCustomPreferenceValue('SignifydDecisionRequest').value; var SignifydPassiveMode = dw.system.Site.getCurrent().getCustomPreferenceValue('SignifydPassiveMode'); + var SignifydSCAEnableSCAEvaluation = dw.system.Site.getCurrent().getCustomPreferenceValue('SignifydSCAEnableSCAEvaluation'); var orderCreationCal = new Calendar(order.creationDate); var paramsObj = { device: { @@ -489,6 +491,9 @@ function setOrderSessionId(order, orderSessionId) { if (SignifydCreateCasePolicy === "PRE_AUTH") { paramsObj.checkoutId = order.getUUID(); + if (SignifydSCAEnableSCAEvaluation && checkSCAPaymentMethod(order)) { + paramsObj.additionalEvalRequests = ["SCA_EVALUATION"]; + } } if (SignifydPassiveMode) { @@ -567,7 +572,11 @@ function getSendTransactionParams(order) { verifications: { avsResponseCode: '', // to be updated by the merchant cvvResponseCode: '', // to be updated by the merchant - } + }, + acquirerDetails: '', // to be updated by the merchant if using SCA + threeDsResult: '', // to be updated by the merchant if using SCA + // uncomment line below if using SCA + // scaExemptionRequested: order.custom.SignifydExemption }], }; @@ -625,6 +634,21 @@ function checkPaymentMethodExclusion(order) { return !result; } +function checkSCAPaymentMethod(order) { + var signifydSCAPaymentMethods = Site.getCurrent().getCustomPreferenceValue('SignifydSCAPaymentMethods'); + var signifydSCAPaymentMethodsArray = signifydSCAPaymentMethods ? signifydSCAPaymentMethods : ""; + var paymentInstruments = order.getPaymentInstruments(); + var result; + + var iterator = paymentInstruments.iterator(); + while(iterator.hasNext()) { + var paymentInstrument = iterator.next(); + result = signifydSCAPaymentMethodsArray.indexOf(paymentInstrument.paymentMethod) > -1; + } + + return result; +} + // eslint-disable-next-line valid-jsdoc /** * Send Signifyd order info and @@ -709,6 +733,17 @@ exports.Call = function (order) { order.custom.SignifydPolicy = answer.decision.checkpointAction; order.custom.SignifydPolicyName = answer.decision.checkpointActionReason; + if (!empty(answer.scaEvaluation)) { + if (!empty(answer.scaEvaluation.outcome)) { + order.custom.SignifydSCAOutcome = answer.scaEvaluation.outcome; + } + if (!empty(answer.scaEvaluation.exemptionDetails)) { + order.custom.SignifydExemption = answer.scaEvaluation.exemptionDetails.exemption; + } + if (!empty(answer.scaEvaluation.exemptionDetails)) { + order.custom.SignifydPlacement = answer.scaEvaluation.exemptionDetails.placement; + } + } } }); diff --git a/link/metadata/meta/system-objecttype-extensions.xml b/link/metadata/meta/system-objecttype-extensions.xml index b280f83..df9bd81 100644 --- a/link/metadata/meta/system-objecttype-extensions.xml +++ b/link/metadata/meta/system-objecttype-extensions.xml @@ -181,6 +181,22 @@ false false + + Signifyd Enable SCA + Enable or disable SCA payment methods + boolean + false + false + false + + + Signifyd SCA Payment Methods + Payment methods IDs, found in: Merchant Tools > Ordering > Payment Methods. + Informing a payment method ID is required if SignifydSCAEnableSCAEvaluation is set to "Yes" + set-of-string + false + false + @@ -190,6 +206,8 @@ + + diff --git a/link/metadata/services.xml b/link/metadata/services.xml index b37e37f..3b4cb85 100644 --- a/link/metadata/services.xml +++ b/link/metadata/services.xml @@ -24,12 +24,6 @@ mWwXvPZDOsusirJxaBwr6dmbUhxzBd8QbVMoCydX5hk= - - https://api.signifyd.com/v2/fulfillments/orderId - signifyd - HjKFxSvEfXB4f/Capj3AHW77ilzFpkWJPV0QbGYrQig= - - 1000 false @@ -84,15 +78,4 @@ SignifydTransaction - - HTTPForm - true - - false - true - false - Signifyd Profile - signifyd.rest.send.fulfillment.development.cred - - diff --git a/sfra/cartridges/app_storefront_base/cartridge/app_storefront_base.properties b/sfra/cartridges/app_storefront_base/cartridge/app_storefront_base.properties index fb3d9ba..0d451b5 100644 --- a/sfra/cartridges/app_storefront_base/cartridge/app_storefront_base.properties +++ b/sfra/cartridges/app_storefront_base/cartridge/app_storefront_base.properties @@ -2,3 +2,4 @@ #Thu Jun 09 11:30:40 EDT 2016 demandware.cartridges.app_storefront_base.multipleLanguageStorefront=true demandware.cartridges.app_storefront_base.id=app_storefront_base +demandware.cartridges.app_storefront_base.version=6.1.0 diff --git a/sfra/cartridges/app_storefront_base/cartridge/client/default/js/campaignBanner.js b/sfra/cartridges/app_storefront_base/cartridge/client/default/js/campaignBanner.js new file mode 100644 index 0000000..733389b --- /dev/null +++ b/sfra/cartridges/app_storefront_base/cartridge/client/default/js/campaignBanner.js @@ -0,0 +1,17 @@ +'use strict'; + +$(document).ready(function () { + if (window.resetCampaignBannerSessionToken) { + window.sessionStorage.removeItem('hide_campaign_banner'); + } + + var campaignBannerStatus = window.sessionStorage.getItem('hide_campaign_banner'); + $('.campaign-banner .close').on('click', function () { + $('.campaign-banner').addClass('d-none'); + window.sessionStorage.setItem('hide_campaign_banner', '1'); + }); + + if (!campaignBannerStatus || campaignBannerStatus < 0) { + $('.campaign-banner').removeClass('d-none'); + } +}); diff --git a/sfra/cartridges/app_storefront_base/cartridge/client/default/js/carousel.js b/sfra/cartridges/app_storefront_base/cartridge/client/default/js/carousel.js new file mode 100644 index 0000000..c20b566 --- /dev/null +++ b/sfra/cartridges/app_storefront_base/cartridge/client/default/js/carousel.js @@ -0,0 +1,174 @@ +'use strict'; +var debounce = require('lodash/debounce'); + +/** + * Get display information related to screen size + * @param {jQuery} element - the current carousel that is being used + * @returns {Object} an object with display information + */ +function screenSize(element) { + var result = { + itemsToDisplay: null, + sufficientSlides: true + }; + var viewSize = $(window).width(); + var extraSmallDisplay = element.data('xs'); + var smallDisplay = element.data('sm'); + var mediumDisplay = element.data('md'); + var numberOfSlides = element.data('number-of-slides'); + + if (viewSize <= 575.98) { + result.itemsToDisplay = extraSmallDisplay; + } else if ((viewSize >= 576) && (viewSize <= 768.98)) { + result.itemsToDisplay = smallDisplay; + } else if (viewSize >= 769) { + result.itemsToDisplay = mediumDisplay; + } + + if (result.itemsToDisplay && numberOfSlides <= result.itemsToDisplay) { + result.sufficientSlides = false; + } + + return result; +} + +/** + * Makes the next element to be displayed next unreachable for screen readers and keyboard nav + * @param {jQuery} element - the current carousel that is being used + */ +function hiddenSlides(element) { + var carousel; + + if (element) { + carousel = element; + } else { + carousel = $('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel'); + } + + var screenSizeInfo = screenSize(carousel); + + var lastDisplayedElement; + var elementToBeDisplayed; + + switch (screenSizeInfo.itemsToDisplay) { + case 2: + lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item'); + elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item'); + break; + case 3: + lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item + .carousel-item'); + elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item'); + break; + case 4: + lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item'); + elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item'); + break; + case 6: + lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item'); + elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item'); + break; + default: + break; + } + + carousel.find('.active.carousel-item').removeAttr('tabindex').removeAttr('aria-hidden'); + carousel.find('.active.carousel-item').find('a, button, details, input, textarea, select') + .removeAttr('tabindex') + .removeAttr('aria-hidden'); + + if (lastDisplayedElement) { + lastDisplayedElement.removeAttr('tabindex').removeAttr('aria-hidden'); + lastDisplayedElement.find('a, button, details, input, textarea, select') + .removeAttr('tabindex') + .removeAttr('aria-hidden'); + } + + if (elementToBeDisplayed) { + elementToBeDisplayed.attr('tabindex', -1).attr('aria-hidden', true); + elementToBeDisplayed.find('a, button, details, input, textarea, select') + .attr('tabindex', -1) + .attr('aria-hidden', true); + } +} + +$(document).ready(function () { + hiddenSlides(); + + $(window).on('resize', debounce(function () { + hiddenSlides(); + }, 500)); + + $('body').on('carousel:setup', function () { + hiddenSlides(); + }); + + $('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('touchstart', function (touchStartEvent) { + var screenSizeInfo = screenSize($(this)); + + if (screenSizeInfo.sufficientSlides) { + var xClick = touchStartEvent.originalEvent.touches[0].pageX; + $(this).one('touchmove', function (touchMoveEvent) { + var xMove = touchMoveEvent.originalEvent.touches[0].pageX; + if (Math.floor(xClick - xMove) > 5) { + $(this).carousel('next'); + } else if (Math.floor(xClick - xMove) < -5) { + $(this).carousel('prev'); + } + }); + $('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('touchend', function () { + $(this).off('touchmove'); + }); + } + }); + + $('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('slide.bs.carousel', function (e) { + var activeCarouselPosition = $(e.relatedTarget).data('position'); + $(this).find('.pd-carousel-indicators .active').removeClass('active'); + $(this).find(".pd-carousel-indicators [data-position='" + activeCarouselPosition + "']").addClass('active'); + + var extraSmallDisplay = $(this).data('xs'); + var smallDisplay = $(this).data('sm'); + var mediumDisplay = $(this).data('md'); + + var arrayOfSlidesToDisplay = []; + + if (!$(this).hasClass('insufficient-xs-slides')) { + arrayOfSlidesToDisplay.push(extraSmallDisplay); + } + + if (!$(this).hasClass('insufficient-sm-slides')) { + arrayOfSlidesToDisplay.push(smallDisplay); + } + + if (!$(this).hasClass('insufficient-md-slides')) { + arrayOfSlidesToDisplay.push(mediumDisplay); + } + + var itemsToDisplay = Math.max.apply(Math, arrayOfSlidesToDisplay); + + var elementIndex = $(e.relatedTarget).index(); + var numberOfSlides = $('.carousel-item', this).length; + var carouselInner = $(this).find('.carousel-inner'); + var carouselItem; + + if (elementIndex >= numberOfSlides - (itemsToDisplay - 1)) { + var it = itemsToDisplay - (numberOfSlides - elementIndex); + for (var i = 0; i < it; i++) { + // append slides to end + if (e.direction === 'left') { + carouselItem = $('.carousel-item', this).eq(i); + + $(carouselItem).appendTo($(carouselInner)); + } else { + carouselItem = $('.carousel-item', this).eq(0); + + $(carouselItem).appendTo($(carouselInner)); + } + } + } + }); + + $('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('slid.bs.carousel', function () { + hiddenSlides($(this)); + }); +}); diff --git a/sfra/cartridges/app_storefront_base/cartridge/client/default/js/cart/cart.js b/sfra/cartridges/app_storefront_base/cartridge/client/default/js/cart/cart.js index e9a33a5..7e1391f 100644 --- a/sfra/cartridges/app_storefront_base/cartridge/client/default/js/cart/cart.js +++ b/sfra/cartridges/app_storefront_base/cartridge/client/default/js/cart/cart.js @@ -1,6 +1,7 @@ 'use strict'; var base = require('../product/base'); +var focusHelper = require('../components/focus'); /** * appends params to a url @@ -41,6 +42,10 @@ function validateBasket(data) { ); $('.number-of-items').empty().append(data.resources.numberOfItems); $('.minicart-quantity').empty().append(data.numItems); + $('.minicart-link').attr({ + 'aria-label': data.resources.minicartCountOfItems, + title: data.resources.minicartCountOfItems + }); $('.minicart .popover').empty(); $('.minicart .popover').removeClass('show'); } @@ -62,7 +67,10 @@ function updateCartTotals(data) { $('.grand-total').empty().append(data.totals.grandTotal); $('.sub-total').empty().append(data.totals.subTotal); $('.minicart-quantity').empty().append(data.numItems); - + $('.minicart-link').attr({ + 'aria-label': data.resources.minicartCountOfItems, + title: data.resources.minicartCountOfItems + }); if (data.totals.orderLevelDiscountTotal.value > 0) { $('.order-discount').removeClass('hide-order-discount'); $('.order-discount-total').empty() @@ -80,7 +88,16 @@ function updateCartTotals(data) { } data.items.forEach(function (item) { - $('.item-' + item.UUID).empty().append(item.renderedPromotions); + if (data.totals.orderLevelDiscountTotal.value > 0) { + $('.coupons-and-promos').empty().append(data.totals.discountsHtml); + } + if (item.renderedPromotions) { + $('.item-' + item.UUID).empty().append(item.renderedPromotions); + } else { + $('.item-' + item.UUID).empty(); + } + $('.uuid-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice); + $('.line-item-price-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice); $('.item-total-' + item.UUID).empty().append(item.priceTotal.renderedPrice); }); } @@ -131,23 +148,25 @@ function updateAvailability(data, uuid) { } } - $('.availability-' + lineItem.UUID).empty(); + if (lineItem != null) { + $('.availability-' + lineItem.UUID).empty(); - if (lineItem.availability) { - if (lineItem.availability.messages) { - lineItem.availability.messages.forEach(function (message) { - messages += '

' + message + '

'; - }); - } + if (lineItem.availability) { + if (lineItem.availability.messages) { + lineItem.availability.messages.forEach(function (message) { + messages += '

' + message + '

'; + }); + } - if (lineItem.availability.inStockDate) { - messages += '

' - + lineItem.availability.inStockDate - + '

'; + if (lineItem.availability.inStockDate) { + messages += '

' + + lineItem.availability.inStockDate + + '

'; + } } - } - $('.availability-' + lineItem.UUID).html(messages); + $('.availability-' + lineItem.UUID).html(messages); + } } /** @@ -156,7 +175,7 @@ function updateAvailability(data, uuid) { * @param {function} match - function that takes an element and returns a boolean indicating if the match is made * @returns {Object|null} - returns an element of the array that matched the query. */ -function findItem(array, match) { +function findItem(array, match) { // eslint-disable-line no-unused-vars for (var i = 0, l = array.length; i < l; i++) { if (match.call(this, array[i])) { return array[i]; @@ -171,52 +190,7 @@ function findItem(array, match) { * @param {string} uuid - The uuid of the product line item to update */ function updateProductDetails(data, uuid) { - var lineItem = findItem(data.cartModel.items, function (item) { - return item.UUID === uuid; - }); - - if (lineItem.variationAttributes) { - var colorAttr = findItem(lineItem.variationAttributes, function (attr) { - return attr.attributeId === 'color'; - }); - - if (colorAttr) { - var colorSelector = '.Color-' + uuid; - var newColor = 'Color: ' + colorAttr.displayValue; - $(colorSelector).text(newColor); - } - - var sizeAttr = findItem(lineItem.variationAttributes, function (attr) { - return attr.attributeId === 'size'; - }); - - if (sizeAttr) { - var sizeSelector = '.Size-' + uuid; - var newSize = 'Size: ' + sizeAttr.displayValue; - $(sizeSelector).text(newSize); - } - - var imageSelector = '.card.product-info.uuid-' + uuid + ' .item-image > img'; - $(imageSelector).attr('src', lineItem.images.small[0].url); - $(imageSelector).attr('alt', lineItem.images.small[0].alt); - $(imageSelector).attr('title', lineItem.images.small[0].title); - } - - var qtySelector = '.quantity[data-uuid="' + uuid + '"]'; - $(qtySelector).val(lineItem.quantity); - $(qtySelector).data('pid', data.newProductId); - - $('.remove-product[data-uuid="' + uuid + '"]').data('pid', data.newProductId); - - var priceSelector = '.line-item-price-' + uuid + ' .sales .value'; - $(priceSelector).text(lineItem.price.sales.formatted); - $(priceSelector).attr('content', lineItem.price.sales.decimalPrice); - - if (lineItem.price.list) { - var listPriceSelector = '.line-item-price-' + uuid + ' .list .value'; - $(listPriceSelector).text(lineItem.price.list.formatted); - $(listPriceSelector).attr('content', lineItem.price.list.decimalPrice); - } + $('.card.product-info.uuid-' + uuid).replaceWith(data.renderedTemplate); } /** @@ -228,13 +202,15 @@ function getModalHtmlElement() { $('#editProductModal').remove(); } var htmlString = '' - + '