diff --git a/_sitegen/controllers/Account.js b/_sitegen/controllers/Account.js new file mode 100644 index 00000000..1e910e59 --- /dev/null +++ b/_sitegen/controllers/Account.js @@ -0,0 +1,504 @@ +'use strict'; + +/** + * Controller that renders the account overview, manages customer registration and password reset, + * and edits customer profile information. + * + * @module controllers/Account + */ + +/* API includes */ +var Resource = require('dw/web/Resource'); +var URLUtils = require('dw/web/URLUtils'); +var Form = require('~/cartridge/scripts/models/FormModel'); +var OrderMgr = require('dw/order/OrderMgr'); + +/* Script Modules */ +var app = require('~/cartridge/scripts/app'); +var guard = require('~/cartridge/scripts/guard'); +var Transaction = require('dw/system/Transaction'); + +/** + * Gets a ContentModel object that wraps the myaccount-home content asset, + * updates the page metadata, and renders the account/accountoverview template. + */ +function show() { + var accountHomeAsset, pageMeta, Content; + + // KLAVIYO + var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'), klid; + if(dw.system.Site.getCurrent().getCustomPreferenceValue('klaviyo_enabled') && !klaviyoUtils.getKlaviyoExchangeID()){ + klid = klaviyoUtils.getProfileInfo(); + } + // END KLAVIYO + + Content = app.getModel('Content'); + accountHomeAsset = Content.get('myaccount-home'); + + pageMeta = require('~/cartridge/scripts/meta'); + pageMeta.update(accountHomeAsset); + + app.getView({downloadAvailable: true, klid: klid}).render('account/accountoverview'); +} + +/** + * Allows a logged in user to download the data from their profile in a csv file. + */ +function datadownload() { + var profile = customer.profile; + var profileDataHelper = require('~/cartridge/scripts/profileDataHelper'); + let response = require('~/cartridge/scripts/util/Response'); + var site = require('dw/system/Site'); + var fileName = site.current.name + '_' + profile.firstName + '_' + profile.lastName + '.json'; + response.renderData(profileDataHelper.getProfileData(profile), fileName); + return; +} + +/** + * Set the consent tracking settings for the session + */ +function consentTracking() { + var consent = request.httpParameterMap.consentTracking.value == 'true'; + session.custom.consentTracking = consent; + session.setTrackingAllowed(consent); +} + +/** + * Clears the profile form and copies customer profile information from the customer global variable + * to the form. Gets a ContentModel object that wraps the myaccount-personaldata content asset, and updates the page + * meta data. Renders the account/user/registration template using an anonymous view. + */ +function editProfile() { + var pageMeta; + var accountPersonalDataAsset; + var Content = app.getModel('Content'); + + if (!request.httpParameterMap.invalid.submitted) { + app.getForm('profile').clear(); + + app.getForm('profile.customer').copyFrom(customer.profile); + app.getForm('profile.login').copyFrom(customer.profile.credentials); + app.getForm('profile.addressbook.addresses').copyFrom(customer.profile.addressBook.addresses); + } + accountPersonalDataAsset = Content.get('myaccount-personaldata'); + + pageMeta = require('~/cartridge/scripts/meta'); + pageMeta.update(accountPersonalDataAsset); + // @FIXME bctext2 should generate out of pagemeta - also action?! + app.getView({ + bctext2: Resource.msg('account.user.registration.editaccount', 'account', null), + Action: 'edit', + ContinueURL: URLUtils.https('Account-EditForm') + }).render('account/user/registration'); +} + +/** + * Handles the form submission on profile update of edit profile. Handles cancel and confirm actions. + * - cancel - clears the profile form and redirects to the Account-Show controller function. + * - confirm - gets a CustomerModel object that wraps the current customer. Validates several form fields. + * If any of the profile validation conditions fail, the user is redirected to the Account-EditProfile controller function. If the profile is valid, the user is redirected to the Account-Show controller function. + */ +function editForm() { + app.getForm('profile').handleAction({ + cancel: function () { + app.getForm('profile').clear(); + response.redirect(URLUtils.https('Account-Show')); + }, + confirm: function () { + var isProfileUpdateValid = true; + var hasEditSucceeded = false; + var Customer = app.getModel('Customer'); + + if (!Customer.checkUserName()) { + app.getForm('profile.customer.email').invalidate(); + isProfileUpdateValid = false; + } + + if (app.getForm('profile.customer.email').value() !== app.getForm('profile.customer.emailconfirm').value()) { + app.getForm('profile.customer.emailconfirm').invalidate(); + isProfileUpdateValid = false; + } + + if (!app.getForm('profile.login.password').value()) { + app.getForm('profile.login.password').invalidate(); + isProfileUpdateValid = false; + } + + if (isProfileUpdateValid) { + hasEditSucceeded = Customer.editAccount(app.getForm('profile.customer.email').value(), app.getForm('profile.login.password').value(), app.getForm('profile.login.password').value(), app.getForm('profile')); + + if (!hasEditSucceeded) { + app.getForm('profile.login.password').invalidate(); + isProfileUpdateValid = false; + } + } + + if (isProfileUpdateValid && hasEditSucceeded) { + response.redirect(URLUtils.https('Account-Show')); + } else { + response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true')); + } + }, + changepassword: function () { + var isProfileUpdateValid = true; + var hasEditSucceeded = false; + var Customer = app.getModel('Customer'); + + if (!Customer.checkUserName()) { + app.getForm('profile.customer.email').invalidate(); + isProfileUpdateValid = false; + } + + if (!app.getForm('profile.login.currentpassword').value()) { + app.getForm('profile.login.currentpassword').invalidate(); + isProfileUpdateValid = false; + } + + if (app.getForm('profile.login.newpassword').value() !== app.getForm('profile.login.newpasswordconfirm').value()) { + app.getForm('profile.login.newpasswordconfirm').invalidate(); + isProfileUpdateValid = false; + } + + if (isProfileUpdateValid) { + hasEditSucceeded = Customer.editAccount(app.getForm('profile.customer.email').value(), app.getForm('profile.login.newpassword').value(), app.getForm('profile.login.currentpassword').value(), app.getForm('profile')); + if (!hasEditSucceeded) { + app.getForm('profile.login.currentpassword').invalidate(); + } + } + + if (isProfileUpdateValid && hasEditSucceeded) { + response.redirect(URLUtils.https('Account-Show')); + } else { + response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true')); + } + }, + error: function () { + response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true')); + } + }); +} + +/** + * Gets the requestpassword form and renders the requestpasswordreset template. This is similar to the password reset + * dialog, but has a screen-based interaction instead of a popup interaction. + */ +function passwordReset() { + app.getForm('requestpassword').clear(); + app.getView({ + ContinueURL: URLUtils.https('Account-PasswordResetForm') + }).render('account/password/requestpasswordreset'); +} + +/** + * Handles form submission from dialog and full page password reset. Handles cancel, send, and error actions. + * - cancel - renders the given template. + * - send - gets a CustomerModel object that wraps the current customer. Gets an EmailModel object that wraps an Email object. + * Checks whether the customer requested the their login password be reset. + * If the customer wants to reset, a password reset token is generated and an email is sent to the customer using the mail/resetpasswordemail template. + * Then the account/password/requestpasswordreset_confirm template is rendered. + * - error - the given template is rendered and passed an error code. + */ +function passwordResetFormHandler(templateName, continueURL) { + var resetPasswordToken, passwordemail; + + app.getForm('profile').handleAction({ + cancel: function () { + app.getView({ + ContinueURL: continueURL + }).render(templateName); + }, + send: function () { + var Customer, resettingCustomer, Email; + Customer = app.getModel('Customer'); + Email = app.getModel('Email'); + var requestForm = Form.get('requestpassword').object.email.htmlValue; + resettingCustomer = Customer.retrieveCustomerByLogin(requestForm); + + if (!empty(resettingCustomer)) { + resetPasswordToken = resettingCustomer.generatePasswordResetToken(); + + passwordemail = Email.get('mail/resetpasswordemail', resettingCustomer.object.profile.email); + passwordemail.setSubject(Resource.msg('resource.passwordassistance', 'email', null)); + passwordemail.send({ + ResetPasswordToken: resetPasswordToken, + Customer: resettingCustomer.object.profile.customer + }); + } + + //for security reasons the same message will be shown for a valid reset password request as for a invalid one + app.getView({ + ErrorCode: null, + ShowContinue: true, + ContinueURL: continueURL + }).render('account/password/requestpasswordreset_confirm'); + }, + error: function () { + app.getView({ + ErrorCode: 'formnotvalid', + ContinueURL: continueURL + }).render(templateName); + } + }); +} + +/** + * The form handler for password resets. + */ +function passwordResetForm() { + passwordResetFormHandler('account/password/requestpasswordreset', URLUtils.https('Account-PasswordResetForm')); +} + +/** + * Clears the requestpassword form and renders the account/password/requestpasswordresetdialog template. + */ +function passwordResetDialog() { + // @FIXME reimplement using dialogify + app.getForm('requestpassword').clear(); + app.getView({ + ContinueURL: URLUtils.https('Account-PasswordResetDialogForm') + }).render('account/password/requestpasswordresetdialog'); +} + +/** + * Handles the password reset form. + */ +function passwordResetDialogForm() { + // @FIXME reimplement using dialogify + passwordResetFormHandler('account/password/requestpasswordresetdialog', URLUtils.https('Account-PasswordResetDialogForm')); +} + +/** + * Gets a CustomerModel wrapping the current customer. Clears the resetpassword form. Checks if the customer wants to reset their password. + * If there is no reset token, redirects to the Account-PasswordReset controller function. If there is a reset token, + * renders the screen for setting a new password. + */ +function setNewPassword() { + var Customer, resettingCustomer; + Customer = app.getModel('Customer'); + + app.getForm('resetpassword').clear(); + resettingCustomer = Customer.getByPasswordResetToken(request.httpParameterMap.Token.getStringValue()); + + if (empty(resettingCustomer)) { + response.redirect(URLUtils.https('Account-PasswordReset')); + } else { + app.getView({ + ContinueURL: URLUtils.https('Account-SetNewPasswordForm') + }).render('account/password/setnewpassword'); + } +} + +/** + * Gets a profile form and handles the cancel and send actions. + * - cancel - renders the setnewpassword template. + * - send - gets a CustomerModel object that wraps the current customer and gets an EmailModel object that wraps an Email object. + * Checks whether the customer can be retrieved using a reset password token. + * If the customer does not have a valid token, the controller redirects to the Account-PasswordReset controller function. + * If they do, then an email is sent to the customer using the mail/setpasswordemail template and the setnewpassword_confirm template is rendered. + * */ +function setNewPasswordForm() { + + app.getForm('profile').handleAction({ + cancel: function () { + app.getView({ + ContinueURL: URLUtils.https('Account-SetNewPasswordForm') + }).render('account/password/setnewpassword'); + return; + }, + send: function () { + var Customer; + var Email; + var passwordChangedMail; + var resettingCustomer; + var success; + + Customer = app.getModel('Customer'); + Email = app.getModel('Email'); + resettingCustomer = Customer.getByPasswordResetToken(request.httpParameterMap.Token.getStringValue()); + + if (!resettingCustomer) { + response.redirect(URLUtils.https('Account-PasswordReset')); + } + + if (app.getForm('resetpassword.password').value() !== app.getForm('resetpassword.passwordconfirm').value()) { + app.getForm('resetpassword.passwordconfirm').invalidate(); + app.getView({ + ContinueURL: URLUtils.https('Account-SetNewPasswordForm') + }).render('account/password/setnewpassword'); + } else { + + success = resettingCustomer.resetPasswordByToken(request.httpParameterMap.Token.getStringValue(), app.getForm('resetpassword.password').value()); + if (!success) { + app.getView({ + ErrorCode: 'formnotvalid', + ContinueURL: URLUtils.https('Account-SetNewPasswordForm') + }).render('account/password/setnewpassword'); + } else { + passwordChangedMail = Email.get('mail/passwordchangedemail', resettingCustomer.object.profile.email); + passwordChangedMail.setSubject(Resource.msg('resource.passwordassistance', 'email', null)); + passwordChangedMail.send({ + Customer: resettingCustomer.object + }); + + app.getView().render('account/password/setnewpassword_confirm'); + } + } + }, + error: function () { + app.getView({ + ErrorCode: 'formnotvalid', + ContinueURL: URLUtils.https('Account-SetNewPasswordForm'), + Token: request.httpParameterMap.Token.getStringValue() + }).render('account/password/setnewpassword'); + } + }); +} + +/** Clears the profile form, adds the email address from login as the profile email address, + * and renders customer registration page. + */ +function startRegister() { + + app.getForm('profile').clear(); + + if (app.getForm('login.username').value() !== null) { + app.getForm('profile.customer.email').object.value = app.getForm('login.username').object.value; + } + + app.getView({ + ContinueURL: URLUtils.https('Account-RegistrationForm') + }).render('account/user/registration'); +} + +/** + * Gets a CustomerModel object wrapping the current customer. + * Gets a profile form and handles the confirm action. + * confirm - validates the profile by checking that the email and password fields: + * - match the emailconfirm and passwordconfirm fields + * - are not duplicates of existing username and password fields for the profile + * If the fields are not valid, the registration template is rendered. + * If the fields are valid, a new customer account is created, the profile form is cleared and + * the customer is redirected to the Account-Show controller function. + */ +function registrationForm() { + app.getForm('profile').handleAction({ + confirm: function () { + var email, emailConfirmation, orderNo, orderUUID, profileValidation, password, passwordConfirmation, existingCustomer, Customer, target; + + Customer = app.getModel('Customer'); + email = app.getForm('profile.customer.email').value(); + emailConfirmation = app.getForm('profile.customer.emailconfirm').value(); + orderNo = app.getForm('profile.customer.orderNo').value(); + orderUUID = app.getForm('profile.customer.orderUUID').value(); + profileValidation = true; + + if (email !== emailConfirmation) { + app.getForm('profile.customer.emailconfirm').invalidate(); + profileValidation = false; + } + + password = app.getForm('profile.login.password').value(); + passwordConfirmation = app.getForm('profile.login.passwordconfirm').value(); + + if (password !== passwordConfirmation) { + app.getForm('profile.login.passwordconfirm').invalidate(); + profileValidation = false; + } + + // Checks if login is already taken. + existingCustomer = Customer.retrieveCustomerByLogin(email); + if (existingCustomer !== null) { + app.getForm('profile.customer.email').invalidate(); + profileValidation = false; + } + + if (profileValidation) { + var customerID = session.customer.ID; + profileValidation = Customer.createAccount(email, password, app.getForm('profile')); + + if (orderNo && orderUUID) { + var order = OrderMgr.getOrder(orderNo); + if (order && customerID === order.customer.ID && order.getUUID() === orderUUID) { + Transaction.wrap(function(){ + order.setCustomer(profileValidation); + }) + session.custom.TargetLocation = URLUtils.https('Account-Show','Registration','true').toString(); + } + } + } + + if (!profileValidation) { + // TODO redirect + app.getView({ + ContinueURL: URLUtils.https('Account-RegistrationForm') + }).render('account/user/registration'); + } else { + app.getForm('profile').clear(); + target = session.custom.TargetLocation; + if (target) { + delete session.custom.TargetLocation; + //@TODO make sure only path, no hosts are allowed as redirect target + dw.system.Logger.info('Redirecting to "{0}" after successful login', target); + response.redirect(target); + } else { + response.redirect(URLUtils.https('Account-Show', 'registration', 'true')); + } + } + } + }); +} + +/** + * Renders the accountnavigation template. + */ +function includeNavigation() { + app.getView().render('account/accountnavigation'); +} + +/* Web exposed methods */ + +/** Renders the account overview. + * @see {@link module:controllers/Account~show} */ +exports.Show = guard.ensure(['get', 'https', 'loggedIn'], show); + +/** returns customer data in json format. + * @see {@link module:controllers/Account~datadownload} */ +exports.DataDownload = guard.ensure(['get', 'https', 'loggedIn'], datadownload); + +/** Renders the account overview. + * @see {@link module:controllers/Account~data} */ +exports.ConsentTracking = guard.ensure(['get'], consentTracking); + +/** Updates the profile of an authenticated customer. + * @see {@link module:controllers/Account~editProfile} */ +exports.EditProfile = guard.ensure(['get', 'https', 'loggedIn'], editProfile); +/** Handles the form submission on profile update of edit profile. + * @see {@link module:controllers/Account~editForm} */ +exports.EditForm = guard.ensure(['post', 'https', 'loggedIn', 'csrf'], editForm); +/** Renders the password reset dialog. + * @see {@link module:controllers/Account~passwordResetDialog} */ +exports.PasswordResetDialog = guard.ensure(['get', 'https'], passwordResetDialog); +/** Renders the password reset screen. + * @see {@link module:controllers/Account~passwordReset} */ +exports.PasswordReset = guard.ensure(['get', 'https'], passwordReset); +/** Handles the password reset form. + * @see {@link module:controllers/Account~passwordResetDialogForm} */ +exports.PasswordResetDialogForm = guard.ensure(['post', 'https', 'csrf'], passwordResetDialogForm); +/** The form handler for password resets. + * @see {@link module:controllers/Account~passwordResetForm} */ +exports.PasswordResetForm = guard.ensure(['post', 'https'], passwordResetForm); +/** Renders the screen for setting a new password. + * @see {@link module:controllers/Account~setNewPassword} */ +exports.SetNewPassword = guard.ensure(['get', 'https'], setNewPassword); +/** Handles the set new password form submit. + * @see {@link module:controllers/Account~setNewPasswordForm} */ +exports.SetNewPasswordForm = guard.ensure(['post', 'https'], setNewPasswordForm); +/** Start the customer registration process and renders customer registration page. + * @see {@link module:controllers/Account~startRegister} */ +exports.StartRegister = guard.ensure(['https'], startRegister); +/** Handles registration form submit. + * @see {@link module:controllers/Account~registrationForm} */ +exports.RegistrationForm = guard.ensure(['post', 'https', 'csrf'], registrationForm); +/** Renders the account navigation. + * @see {@link module:controllers/Account~includeNavigation} */ +exports.IncludeNavigation = guard.ensure(['get'], includeNavigation); diff --git a/_sitegen/controllers/COCustomer.js b/_sitegen/controllers/COCustomer.js new file mode 100644 index 00000000..8cd25c05 --- /dev/null +++ b/_sitegen/controllers/COCustomer.js @@ -0,0 +1,118 @@ +'use strict'; + +/** + * Controller for the first step of the cart checkout process, which is to ask the customer to login, register, or + * checkout anonymously. + * + * @module controllers/COCustomer + */ + +/* API Includes */ +var Transaction = require('dw/system/Transaction'); +var URLUtils = require('dw/web/URLUtils'); + +/* Script Modules */ +var app = require('~/cartridge/scripts/app'); +var guard = require('~/cartridge/scripts/guard'); + +var Cart = require('~/cartridge/scripts/models/CartModel'); +var Content = require('~/cartridge/scripts/models/ContentModel'); + +/** + * First step of the checkout is to choose the checkout type: returning, guest or create account checkout. + * Prepares the checkout initially: removes all payment instruments from the basket and clears all + * forms used in the checkout process, when the customer enters the checkout. The single steps (shipping, billing etc.) + * may not contain the form clearing, in order to support navigating forth and back in the checkout steps without losing + * already entered form values. + */ +function start() { + var oauthLoginForm = app.getForm('oauthlogin'); + app.getForm('singleshipping').clear(); + app.getForm('multishipping').clear(); + app.getForm('billing').clear(); + + Transaction.wrap(function () { + Cart.goc().removeAllPaymentInstruments(); + }); + + /* Klaviyo Started Checkout event tracking */ + var basketMgr = require('dw/order/BasketMgr'); + var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); + var startedCheckoutData = require('*/cartridge/scripts/klaviyo/eventData/startedCheckout'); + if(dw.system.Site.getCurrent().getCustomPreferenceValue('klaviyo_enabled')){ + var exchangeID = klaviyoUtils.getKlaviyoExchangeID(); + var dataObj, serviceCallResult, currentBasket; + if (exchangeID) { + currentBasket = basketMgr.getCurrentBasket() + if (currentBasket && currentBasket.getProductLineItems().toArray().length) { //TODO: is there a property for isEmpty on basket object? + dataObj = startedCheckoutData.getData(currentBasket); + serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, klaviyoUtils.EVENT_NAMES.startedCheckout); + } + } + } + /* END Klaviyo Started Checkout event tracking */ + + + // Direct to first checkout step if already authenticated. + if (customer.authenticated) { + response.redirect(URLUtils.https('COShipping-Start')); + return; + } else { + var loginForm = app.getForm('login'); + loginForm.clear(); + oauthLoginForm.clear(); + + // Prepopulate login form field with customer's login name. + if (customer.registered) { + loginForm.setValue('username', customer.profile.credentials.login); + } + + var loginAsset = Content.get('myaccount-login'); + + var pageMeta = require('~/cartridge/scripts/meta'); + pageMeta.update(loginAsset); + + app.getView({ + ContinueURL: URLUtils.https('COCustomer-LoginForm').append('scope', 'checkout') + }).render('checkout/checkoutlogin'); + } + +} + +/** + * Form handler for the login form. Handles the following actions: + * - __login__ - Calls the {@link module:controllers/Login~process|Login controller Process function}. If this returns successfully, calls + * the {@link module:controllers/COShipping~Start|COShipping controller Start function}. + * - __register__ - Calls the {@link module:controllers/Account~StartRegister|Account controller StartRegister function}. + * - __unregistered__ - Calls the {@link module:controllers/COShipping~Start|COShipping controller Start function}. + */ +function showLoginForm() { + var loginForm = app.getForm('login'); + session.custom.TargetLocation = URLUtils.https('COShipping-Start').toString(); + + loginForm.handleAction({ + login: function () { + app.getController('Login').LoginForm(); + }, + register: function () { + response.redirect(URLUtils.https('Account-StartRegister')); + }, + unregistered: function () { + response.redirect(URLUtils.https('COShipping-Start')); + } + }); +} + +/* + * Module exports + */ + +/* + * Web exposed methods + */ +/** Selects the type of checkout: returning, guest, or create account. The first step in the checkout process. + * @see module:controllers/COCustomer~start */ +exports.Start = guard.ensure(['https'], start); +/** Form handler for the login form. + * @see module:controllers/COCustomer~showLoginForm */ +exports.LoginForm = guard.ensure(['https', 'post', 'csrf'], showLoginForm); diff --git a/_sitegen/controllers/COSummary.js b/_sitegen/controllers/COSummary.js new file mode 100644 index 00000000..36053f2c --- /dev/null +++ b/_sitegen/controllers/COSummary.js @@ -0,0 +1,138 @@ +'use strict'; + +/** + * This controller implements the last step of the checkout. A successful handling + * of billing address and payment method selection leads to this controller. It + * provides the customer with a last overview of the basket prior to confirm the + * final order creation. + * + * @module controllers/COSummary + */ + +/* API Includes */ +var Resource = require('dw/web/Resource'); +var Transaction = require('dw/system/Transaction'); +var URLUtils = require('dw/web/URLUtils'); + +/* Script Modules */ +var app = require('~/cartridge/scripts/app'); +var guard = require('~/cartridge/scripts/guard'); + +var Cart = app.getModel('Cart'); + +/** + * Renders the summary page prior to order creation. + * @param {Object} context context object used for the view + */ +function start(context) { + var cart = Cart.get(); + + // Checks whether all payment methods are still applicable. Recalculates all existing non-gift certificate payment + // instrument totals according to redeemed gift certificates or additional discounts granted through coupon + // redemptions on this page. + var COBilling = app.getController('COBilling'); + if (!COBilling.ValidatePayment(cart)) { + COBilling.Start(); + return; + } else { + Transaction.wrap(function () { + cart.calculate(); + }); + + Transaction.wrap(function () { + if (!cart.calculatePaymentTransactionTotal()) { + COBilling.Start(); + } + }); + + var pageMeta = require('~/cartridge/scripts/meta'); + var viewContext = require('app_storefront_core/cartridge/scripts/common/extend').immutable(context, { + Basket: cart.object + }); + pageMeta.update({pageTitle: Resource.msg('summary.meta.pagetitle', 'checkout', 'SiteGenesis Checkout')}); + app.getView(viewContext).render('checkout/summary/summary'); + } +} + +/** + * This function is called when the "Place Order" action is triggered by the + * customer. + */ +function submit() { + // Calls the COPlaceOrder controller that does the place order action and any payment authorization. + // COPlaceOrder returns a JSON object with an order_created key and a boolean value if the order was created successfully. + // If the order creation failed, it returns a JSON object with an error key and a boolean value. + var placeOrderResult = app.getController('COPlaceOrder').Start(); + if (placeOrderResult.error) { + start({ + PlaceOrderError: placeOrderResult.PlaceOrderError + }); + } else if (placeOrderResult.order_created) { + showConfirmation(placeOrderResult.Order); + } +} + +/** + * Renders the order confirmation page after successful order + * creation. If a nonregistered customer has checked out, the confirmation page + * provides a "Create Account" form. This function handles the + * account creation. + */ +function showConfirmation(order) { + if (!customer.authenticated) { + // Initializes the account creation form for guest checkouts by populating the first and last name with the + // used billing address. + var customerForm = app.getForm('profile.customer'); + customerForm.setValue('firstname', order.billingAddress.firstName); + customerForm.setValue('lastname', order.billingAddress.lastName); + customerForm.setValue('email', order.customerEmail); + customerForm.setValue('orderNo', order.orderNo); + customerForm.setValue('orderUUID', order.getUUID()); + } + + /* Klaviyo Order Confirmation event tracking */ + //var OrderMgr = require('dw/order/OrderMgr'); + var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); + var orderConfirmationData = require('*/cartridge/scripts/klaviyo/eventData/orderConfirmation'); + if(dw.system.Site.getCurrent().getCustomPreferenceValue('klaviyo_enabled')){ + var exchangeID = klaviyoUtils.getKlaviyoExchangeID(); + var dataObj, serviceCallResult; //, currentOrder; + if (exchangeID && order) { + // check to see if the status is new or created + if (order.status == dw.order.Order.ORDER_STATUS_NEW || order.status == dw.order.Order.ORDER_STATUS_OPEN) { + dataObj = orderConfirmationData.getData(order, exchangeID); + serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, klaviyoUtils.EVENT_NAMES.orderConfirmation); + } + + } + } + /* ENDKlaviyo Order Confirmation event tracking */ + + + app.getForm('profile.login.passwordconfirm').clear(); + app.getForm('profile.login.password').clear(); + + var pageMeta = require('~/cartridge/scripts/meta'); + pageMeta.update({pageTitle: Resource.msg('confirmation.meta.pagetitle', 'checkout', 'SiteGenesis Checkout Confirmation')}); + app.getView({ + Order: order, + ContinueURL: URLUtils.https('Account-RegistrationForm') // needed by registration form after anonymous checkouts + }).render('checkout/confirmation/confirmation'); +} + +/* + * Module exports + */ + +/* + * Web exposed methods + */ +/** @see module:controllers/COSummary~Start */ +exports.Start = guard.ensure(['https'], start); +/** @see module:controllers/COSummary~Submit */ +exports.Submit = guard.ensure(['https', 'post', 'csrf'], submit); + +/* + * Local method + */ +exports.ShowConfirmation = showConfirmation; diff --git a/_sitegen/controllers/Cart.js b/_sitegen/controllers/Cart.js new file mode 100644 index 00000000..424d48ab --- /dev/null +++ b/_sitegen/controllers/Cart.js @@ -0,0 +1,495 @@ +'use strict'; + +/** + * Controller that adds and removes products and coupons in the cart. + * Also provides functions for the continue shopping button and minicart. + * + * @module controllers/Cart + */ + +/* API Includes */ +var ArrayList = require('dw/util/ArrayList'); +var ISML = require('dw/template/ISML'); +var Resource = require('dw/web/Resource'); +var Transaction = require('dw/system/Transaction'); +var URLUtils = require('dw/web/URLUtils'); + +/* Script Modules */ +var app = require('~/cartridge/scripts/app'); +var guard = require('~/cartridge/scripts/guard'); + +/** + * Redirects the user to the last visited catalog URL if known, otherwise redirects to + * a hostname-only URL if an alias is set, or to the Home-Show controller function in the default + * format using the HTTP protocol. + */ +function continueShopping() { + + var location = require('~/cartridge/scripts/util/Browsing').lastCatalogURL(); + + if (location) { + response.redirect(location); + } else { + response.redirect(URLUtils.httpHome()); + } +} + +/** + * Invalidates the login and shipment forms. Renders the checkout/cart/cart template. + */ +function show() { + var cartForm = app.getForm('cart'); + app.getForm('login').invalidate(); + + cartForm.get('shipments').invalidate(); + + // KLAVIYO + var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'), klid; + if(dw.system.Site.getCurrent().getCustomPreferenceValue('klaviyo_enabled') && !klaviyoUtils.getKlaviyoExchangeID()){ + klid = klaviyoUtils.getProfileInfo(); + } + // END KLAVIYO + + app.getView('Cart', { + cart: app.getModel('Cart').get(), + RegistrationStatus: false, + klid: klid + }).render('checkout/cart/cart'); + +} + +/** + * Handles the form actions for the cart. + * - __addCoupon(formgroup)__ - adds a coupon to the basket in a transaction. Returns a JSON object with parameters for the template. + * - __calculateTotal__ - returns the cart object. + * - __checkoutCart__ - validates the cart for checkout. If valid, redirect to the COCustomer-Start controller function to start the checkout. If invalid returns the cart and the results of the validation. + * - __continueShopping__ - calls the {@link module:controllers/Cart~continueShopping|continueShopping} function and returns null. + * - __deleteCoupon(formgroup)__ - removes a coupon from the basket in a transaction. Returns a JSON object with parameters for the template + * - __deleteGiftCertificate(formgroup)__ - removes a gift certificate from the basket in a transaction. Returns a JSON object with parameters for the template. + * - __deleteProduct(formgroup)__ - removes a product from the basket in a transaction. Returns a JSON object with parameters for the template. + * - __editLineItem(formgroup)__ - gets a ProductModel that wraps the pid (product ID) in the httpParameterMap and updates the options to select for the product. Updates the product in a transaction. + * Renders the checkout/cart/refreshcart template. Returns null. + * - __login__ - calls the Login controller and returns a JSON object with parameters for the template. + * - __logout__ - logs the customer out and returns a JSON object with parameters for the template. + * - __register__ - calls the Account controller StartRegister function. Updates the cart calculation in a transaction and returns null. + * - __unregistered__ - calls the COShipping controller Start function and returns null. + * - __updateCart__ - In a transaction, removes zero quantity line items, removes line items for in-store pickup, and copies data to system objects based on the form bindings. + * Returns a JSON object with parameters for the template. + * - __error__ - returns null. + * + * __Note:__ The CartView sets the ContinueURL to this function, so that any time URLUtils.continueURL() is used in the cart.isml, this function is called. + * Several actions have formgroup as an input parameter. The formgroup is supplied by the {@link module:models/FormModel~FormModel/handleAction|FormModel handleAction} function in the FormModel module. + * The formgroup is session.forms.cart object of the triggered action in the form definition. Any object returned by the function for an action is passed in the parameters to the cart template + * and is accessible using the $pdict.property syntax. For example, if a function returns {CouponStatus: status} is accessible via ${pdict.CouponStatus} + * Most member functions return a JSON object that contains {cart: cart}. The cart property is used by the CartView to determine the value of + * $pdict.Basket in the cart.isml template. + * + * For any member function that returns an object, the page metadata is updated, the function gets a ContentModel that wraps the cart content asset, + * and the checkout/cart/cart template is rendered. + * + */ +function submitForm() { + // There is no existing state, so resolve the basket again. + var cart, formResult, cartForm, cartAsset, pageMeta; + cartForm = app.getForm('cart'); + cart = app.getModel('Cart').goc(); + + formResult = cartForm.handleAction({ + //Add a coupon if a coupon was entered correctly and is active. + 'addCoupon': function (formgroup) { + var CSRFProtection = require('dw/web/CSRFProtection'); + + if (!CSRFProtection.validateRequest()) { + app.getModel('Customer').logout(); + app.getView().render('csrf/csrffailed'); + return null; + } + + var status; + var result = { + cart: cart, + EnableCheckout: true, + dontRedirect: true + }; + + if (formgroup.couponCode.htmlValue) { + status = cart.addCoupon(formgroup.couponCode.htmlValue); + + if (status) { + // if a status is returned, set the error state based on whether or not it was applied + var statusError = (status.CouponStatus != 'APPLIED'); + + result.dontRedirect = statusError; + result.CouponStatus = { + code: status.CouponStatus, + error: statusError + }; + } else { + // no status means valid but inactive coupon + result.CouponError = 'NO_ACTIVE_PROMOTION'; + } + } else { + // no coupon code supplied + result.CouponError = 'COUPON_CODE_MISSING'; + } + return result; + }, + 'calculateTotal': function () { + // Nothing to do here as re-calculation happens during view anyways + return { + cart: cart + }; + }, + 'checkoutCart': function () { + var validationResult, result; + + validationResult = cart.validateForCheckout(); + + if (validationResult.EnableCheckout) { + //app.getController('COCustomer').Start(); + response.redirect(URLUtils.https('COCustomer-Start')); + + } else { + result = { + cart: cart, + BasketStatus: validationResult.BasketStatus, + EnableCheckout: validationResult.EnableCheckout + }; + } + return result; + }, + 'continueShopping': function () { + continueShopping(); + return null; + }, + 'deleteCoupon': function (formgroup) { + Transaction.wrap(function () { + cart.removeCouponLineItem(formgroup.getTriggeredAction().object); + }); + + return { + cart: cart + }; + }, + 'deleteGiftCertificate': function (formgroup) { + Transaction.wrap(function () { + cart.removeGiftCertificateLineItem(formgroup.getTriggeredAction().object); + }); + + return { + cart: cart + }; + }, + 'deleteProduct': function (formgroup) { + Transaction.wrap(function () { + cart.removeProductLineItem(formgroup.getTriggeredAction().object); + }); + + return { + cart: cart + }; + }, + 'editLineItem': function (formgroup) { + var product, productOptionModel; + product = app.getModel('Product').get(request.httpParameterMap.pid.stringValue).object; + productOptionModel = product.updateOptionSelection(request.httpParameterMap); + + Transaction.wrap(function () { + cart.updateLineItem(formgroup.getTriggeredAction().object, product, request.httpParameterMap.Quantity.doubleValue, productOptionModel); + cart.calculate(); + }); + + ISML.renderTemplate('checkout/cart/refreshcart'); + return null; + }, + 'updateCart': function () { + + Transaction.wrap(function () { + var shipmentItem, item; + + // remove zero quantity line items + for (var i = 0; i < session.forms.cart.shipments.childCount; i++) { + shipmentItem = session.forms.cart.shipments[i]; + + for (var j = 0; j < shipmentItem.items.childCount; j++) { + item = shipmentItem.items[j]; + + if (item.quantity.value === 0) { + cart.removeProductLineItem(item.object); + } + } + } + + session.forms.cart.shipments.accept(); + cart.checkInStoreProducts(); + }); + + return { + cart: cart, + EnableCheckout: true + }; + }, + 'error': function () { + return null; + } + }); + + if (formResult) { + cartAsset = app.getModel('Content').get('cart'); + + pageMeta = require('~/cartridge/scripts/meta'); + pageMeta.update(cartAsset); + + if (formResult.dontRedirect) { + app.getView({ + Basket: formResult.cart.object, + EnableCheckout: formResult.EnableCheckout, + CouponStatus: formResult.CouponStatus, + CouponError: formResult.CouponError + }).render('checkout/cart/cart'); + } else { + response.redirect(URLUtils.https('Cart-Show')); + } + } +} + +/** + * Adds or replaces a product in the cart, gift registry, or wishlist. + * If the function is being called as a gift registry update, calls the + * {@link module:controllers/GiftRegistry~replaceProductListItem|GiftRegistry controller ReplaceProductListItem function}. + * The httpParameterMap source and cartAction parameters indicate how the function is called. + * If the function is being called as a wishlist update, calls the + * {@link module:controllers/Wishlist~replaceProductListItem|Wishlist controller ReplaceProductListItem function}. + * If the product line item for the product to add has a: + * - __uuid__ - gets a ProductModel that wraps the product and determines the product quantity and options. + * In a transaction, calls the {@link module:models/CartModel~CartModel/updateLineItem|CartModel updateLineItem} function to replace the current product in the line + * item with the new product. + * - __plid__ - gets the product list and adds a product list item. + * Otherwise, adds the product and checks if a new discount line item is triggered. + * Renders the checkout/cart/refreshcart template if the httpParameterMap format parameter is set to ajax, + * otherwise renders the checkout/cart/cart template. + */ +function addProduct() { + var cart = app.getModel('Cart').goc(); + var renderInfo = cart.addProductToCart(); + + /* Klaviyo Added to Cart event tracking */ + var basketMgr = require('dw/order/BasketMgr'); + var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); + var addedToCartData = require('*/cartridge/scripts/klaviyo/eventData/addedToCart'); + if(dw.system.Site.getCurrent().getCustomPreferenceValue('klaviyo_enabled')){ + var exchangeID = klaviyoUtils.getKlaviyoExchangeID(); + var dataObj, serviceCallResult, currentBasket; + if (exchangeID) { + currentBasket = basketMgr.getCurrentBasket() + if (currentBasket && currentBasket.getProductLineItems().toArray().length) { //TODO: is there a property for isEmpty on basket object? + dataObj = addedToCartData.getData(currentBasket); + serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, klaviyoUtils.EVENT_NAMES.addedToCart); + // TODO: need to do anything here with the service call result, or handle all errs etc within trackEvent? otherwise no need to assign to a var / return a value + } + } + } + /* END Klaviyo Added to Cart event tracking */ + + if (renderInfo.source === 'giftregistry') { + app.getView().render('account/giftregistry/refreshgiftregistry'); + } else if (renderInfo.template === 'checkout/cart/cart') { + app.getView('Cart', { + Basket: cart + }).render(renderInfo.template); + } else if (renderInfo.format === 'ajax') { + app.getView('Cart', { + cart: cart, + BonusDiscountLineItem: renderInfo.BonusDiscountLineItem + }).render(renderInfo.template); + } else { + response.redirect(URLUtils.url('Cart-Show')); + } +} + +/** + * Displays the current items in the cart in the minicart panel. + */ +function miniCart() { + + var cart = app.getModel('Cart').get(); + app.getView({ + Basket: cart ? cart.object : null + }).render('checkout/cart/minicart'); + +} + +/** + * Adds the product with the given ID to the wish list. + * + * Gets a ProductModel that wraps the product in the httpParameterMap. Uses + * {@link module:models/ProductModel~ProductModel/updateOptionSelection|ProductModel updateOptionSelection} + * to get the product options selected for the product. + * Gets a ProductListModel and adds the product to the product list. Renders the checkout/cart/cart template. + */ +function addToWishlist() { + var productID, product, productOptionModel, productList, Product; + Product = app.getModel('Product'); + + productID = request.httpParameterMap.pid.stringValue; + product = Product.get(productID); + productOptionModel = product.updateOptionSelection(request.httpParameterMap); + + productList = app.getModel('ProductList').get(); + productList.addProduct(product.object, request.httpParameterMap.Quantity.doubleValue, productOptionModel); + + app.getView('Cart', { + cart: app.getModel('Cart').get(), + ProductAddedToWishlist: productID + }).render('checkout/cart/cart'); + +} + +/** + * Adds a bonus product to the cart. + * + * Parses the httpParameterMap and adds the bonus products in it to an array. + * + * Gets the bonus discount line item. In a transaction, removes the bonus discount line item. For each bonus product in the array, + * gets the product based on the product ID and adds the product as a bonus product to the cart. + * + * If the product is a bundle, updates the product option selections for each child product, finds the line item, + * and replaces it with the current child product and selections. + * + * If the product and line item can be retrieved, recalculates the cart, commits the transaction, and renders a JSON object indicating success. + * If the transaction fails, rolls back the transaction and renders a JSON object indicating failure. + */ +function addBonusProductJson() { + var h, i, j, cart, data, productsJSON, bonusDiscountLineItem, product, lineItem, childPids, childProduct, foundLineItem, Product; + cart = app.getModel('Cart').goc(); + Product = app.getModel('Product'); + + // parse bonus product JSON + data = JSON.parse(request.httpParameterMap.getRequestBodyAsString()); + productsJSON = new ArrayList(); + + for (h = 0; h < data.bonusproducts.length; h += 1) { + // add bonus product at index zero (front of the array) each time + productsJSON.addAt(0, data.bonusproducts[h].product); + } + + bonusDiscountLineItem = cart.getBonusDiscountLineItemByUUID(request.httpParameterMap.bonusDiscountLineItemUUID.stringValue); + + Transaction.begin(); + cart.removeBonusDiscountLineItemProducts(bonusDiscountLineItem); + + for (i = 0; i < productsJSON.length; i += 1) { + + product = Product.get(productsJSON[i].pid).object; + lineItem = cart.addBonusProduct(bonusDiscountLineItem, product, new ArrayList(productsJSON[i].options), parseInt(productsJSON[i].qty)); + + if (lineItem && product) { + if (product.isBundle()) { + + childPids = productsJSON[i].childPids.split(','); + + for (j = 0; j < childPids.length; j += 1) { + childProduct = Product.get(childPids[j]).object; + + if (childProduct) { + + // TODO: CommonJSify cart/UpdateProductOptionSelections.ds and import here + + var UpdateProductOptionSelections = require('app_storefront_core/cartridge/scripts/cart/UpdateProductOptionSelections'); + UpdateProductOptionSelections.update({ + SelectedOptions: new ArrayList(productsJSON[i].options), + Product: childProduct + }); + + foundLineItem = cart.getBundledProductLineItemByPID(lineItem.getBundledProductLineItems(), + (childProduct.isVariant() ? childProduct.masterProduct.ID : childProduct.ID)); + + if (foundLineItem) { + foundLineItem.replaceProduct(childProduct); + } + } + } + } + } else { + Transaction.rollback(); + + let r = require('~/cartridge/scripts/util/Response'); + r.renderJSON({ + success: false + }); + return; + } + } + + cart.calculate(); + Transaction.commit(); + + let r = require('~/cartridge/scripts/util/Response'); + r.renderJSON({ + success: true + }); +} + +/** + * Adds a coupon to the cart using JSON. + * + * Gets the CartModel. Gets the coupon code from the httpParameterMap couponCode parameter. + * In a transaction, adds the coupon to the cart and renders a JSON object that includes the coupon code + * and the status of the transaction. + * + */ +function addCouponJson() { + var couponCode, cart, couponStatus; + + couponCode = request.httpParameterMap.couponCode.stringValue; + cart = app.getModel('Cart').goc(); + + Transaction.wrap(function () { + couponStatus = cart.addCoupon(couponCode); + }); + + if (request.httpParameterMap.format.stringValue === 'ajax') { + let r = require('~/cartridge/scripts/util/Response'); + r.renderJSON({ + status: couponStatus.code, + message: Resource.msgf('cart.' + couponStatus.code, 'checkout', null, couponCode), + success: !couponStatus.error, + baskettotal: cart.object.adjustedMerchandizeTotalGrossPrice.value, + CouponCode: couponCode + }); + } +} + +/* +* Module exports +*/ + +/* +* Exposed methods. +*/ +/** Adds a product to the cart. + * @see {@link module:controllers/Cart~addProduct} */ +exports.AddProduct = guard.ensure(['post'], addProduct); +/** Invalidates the login and shipment forms. Renders the basket content. + * @see {@link module:controllers/Cart~show} */ +exports.Show = guard.ensure(['https'], show); +/** Form handler for the cart form. + * @see {@link module:controllers/Cart~submitForm} */ +exports.SubmitForm = guard.ensure(['post', 'https'], submitForm); +/** Redirects the user to the last visited catalog URL. + * @see {@link module:controllers/Cart~continueShopping} */ +exports.ContinueShopping = guard.ensure(['https'], continueShopping); +/** Adds a coupon to the cart using JSON. Called during checkout. + * @see {@link module:controllers/Cart~addCouponJson} */ +exports.AddCouponJson = guard.ensure(['get', 'https'], addCouponJson); +/** Displays the current items in the cart in the minicart panel. + * @see {@link module:controllers/Cart~miniCart} */ +exports.MiniCart = guard.ensure(['get'], miniCart); +/** Adds the product with the given ID to the wish list. + * @see {@link module:controllers/Cart~addToWishlist} */ +exports.AddToWishlist = guard.ensure(['get', 'https', 'loggedIn'], addToWishlist, { + scope: 'wishlist' +}); +/** Adds bonus product to cart. + * @see {@link module:controllers/Cart~addBonusProductJson} */ +exports.AddBonusProduct = guard.ensure(['post'], addBonusProductJson); diff --git a/cartridges/int_klaviyo/cartridge/controllers/Klaviyo.js b/cartridges/int_klaviyo/cartridge/controllers/Klaviyo.js index af859ded..0e9f3edf 100644 --- a/cartridges/int_klaviyo/cartridge/controllers/Klaviyo.js +++ b/cartridges/int_klaviyo/cartridge/controllers/Klaviyo.js @@ -1,6 +1,7 @@ 'use strict'; /* Script Modules */ +var app = require('*/cartridge/scripts/app'); var guard = require('*/cartridge/scripts/guard'); /* eslint-disable */ @@ -33,25 +34,29 @@ var Event = function () { action = request.httpParameterMap.action.stringValue; parms = request.httpParameterMap.parms.stringValue; - var foo = 'bar'; - - switch(action) { - case klaviyoUtils.EVENT_NAMES.viewedProduct : - dataObj = viewedProductData.getData(parms); // parms: product ID - break; - case klaviyoUtils.EVENT_NAMES.viewedCategory : - dataObj = viewedCategoryData.getData(parms); // parms: category ID - break; - case klaviyoUtils.EVENT_NAMES.searchedSite : - // TODO: add Show-Ajax append? test to be sure when this happens... if its just on paging, do we want to track that? - // TODO: what about search-suggestion flyout? probably not supportable - // TODO: be sure to check for 0 result searches, filtering on both search results and PLPs, re-sorts, etc and get clarity on requirements - parms = parms.split('|'); - dataObj = searchedSiteData.getData(parms[0], parms[1]); // parms: search phrase, result count - break; + if(action != 'false') { // string test intentional, action passed as 'false' for pages that do not need to trigger events (Home, Page, Default) + switch(action) { + case klaviyoUtils.EVENT_NAMES.viewedProduct : + dataObj = viewedProductData.getData(parms); // parms: product ID + break; + case klaviyoUtils.EVENT_NAMES.viewedCategory : + dataObj = viewedCategoryData.getData(parms); // parms: category ID + break; + case klaviyoUtils.EVENT_NAMES.searchedSite : + // TODO: add Show-Ajax append? test to be sure when this happens... if its just on paging, do we want to track that? + // TODO: what about search-suggestion flyout? probably not supportable + // TODO: be sure to check for 0 result searches, filtering on both search results and PLPs, re-sorts, etc and get clarity on requirements + parms = parms.split('|'); + dataObj = searchedSiteData.getData(parms[0], parms[1]); // parms: search phrase, result count + break; + } + serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, action); + // TODO: need to do anything here with the service call result, or handle all errs etc within trackEvent? otherwise no need to assign to a var / return a value } - serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, action); - // TODO: need to do anything here with the service call result, or handle all errs etc within trackEvent? otherwise no need to assign to a var / return a value + } else { + // no klaviyo ID, check for SFCC profile and ID off that if extent + var klid = klaviyoUtils.getProfileInfo(); + app.getView({klid: klid}).render('klaviyo/klaviyoID'); } } diff --git a/cartridges/int_klaviyo/cartridge/templates/default/klaviyo/klaviyoFooter.isml b/cartridges/int_klaviyo/cartridge/templates/default/klaviyo/klaviyoFooter.isml index 2930acb7..13d3911d 100644 --- a/cartridges/int_klaviyo/cartridge/templates/default/klaviyo/klaviyoFooter.isml +++ b/cartridges/int_klaviyo/cartridge/templates/default/klaviyo/klaviyoFooter.isml @@ -8,11 +8,20 @@ // klaviyo object loader - provided by klaviyo !function(){if(!window.klaviyo){window._klOnsite=window._klOnsite||[];try{window.klaviyo=new Proxy({},{get:function(n,i){return"push"===i?function(){var n;(n=window._klOnsite).push.apply(n,arguments)}:function(){for(var n=arguments.length,o=new Array(n),w=0;w - + + + klAction and klParms are used to pass event information to controllers that require events to be tracked. (PDP, PLP and Search) + they are passed as false to pages that only need to try to ID the user off the SFCC Profile (Home) + + + - + @@ -21,7 +30,6 @@ - xxx ${klAction} ${request.httpParameterMap.cgid.stringValue} xxx diff --git a/cartridges/int_klaviyo_sfra/cartridge/controllers/Home.js b/cartridges/int_klaviyo_sfra/cartridge/controllers/Home.js deleted file mode 100644 index 18cc749a..00000000 --- a/cartridges/int_klaviyo_sfra/cartridge/controllers/Home.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -var server = require('server'); -var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); - -server.extend(module.superModule); - -server.append('Show', function (req, res, next) { - if(klaviyoUtils.klaviyoEnabled && !klaviyoUtils.getKlaviyoExchangeID()){ - res.viewData.klid = klaviyoUtils.getProfileInfo(); - } - next(); -}); - - -module.exports = server.exports(); \ No newline at end of file diff --git a/cartridges/int_klaviyo_sfra/cartridge/controllers/Klaviyo.js b/cartridges/int_klaviyo_sfra/cartridge/controllers/Klaviyo.js index 4198c8fc..9b439cc0 100644 --- a/cartridges/int_klaviyo_sfra/cartridge/controllers/Klaviyo.js +++ b/cartridges/int_klaviyo_sfra/cartridge/controllers/Klaviyo.js @@ -13,6 +13,7 @@ var searchedSiteData = require('*/cartridge/scripts/klaviyo/eventData/searchedSi * for event tracking on pages whose controllers are not cached OOTB, server.appends to those OOTB controllers should be utilized * reference Cart.js, Checkout.js, Order.js in the int_klaviyo_sfra cartridge * + * Also note that this route gets called via remote include for Home-Show, Page-Show and Default-Start only to check for identifying users to Klaviyo off the user's SFCC Profile ***/ @@ -29,23 +30,28 @@ server.get('Event', function (req, res, next) { action = request.httpParameterMap.action.stringValue; parms = request.httpParameterMap.parms.stringValue; - switch(action) { - case klaviyoUtils.EVENT_NAMES.viewedProduct : - dataObj = viewedProductData.getData(parms); // parms: product ID - break; - case klaviyoUtils.EVENT_NAMES.viewedCategory : - dataObj = viewedCategoryData.getData(parms); // parms: category ID - break; - case klaviyoUtils.EVENT_NAMES.searchedSite : - // TODO: add Show-Ajax append? test to be sure when this happens... if its just on paging, do we want to track that? - // TODO: what about search-suggestion flyout? probably not supportable - // TODO: be sure to check for 0 result searches, filtering on both search results and PLPs, re-sorts, etc and get clarity on requirements - parms = parms.split('|'); - dataObj = searchedSiteData.getData(parms[0], parms[1]); // parms: search phrase, result count - break; + if(action != 'false') { // string test intentional, action passed as 'false' for pages that do not need to trigger events (Home, Page, Default) + switch(action) { + case klaviyoUtils.EVENT_NAMES.viewedProduct : + dataObj = viewedProductData.getData(parms); // parms: product ID + break; + case klaviyoUtils.EVENT_NAMES.viewedCategory : + dataObj = viewedCategoryData.getData(parms); // parms: category ID + break; + case klaviyoUtils.EVENT_NAMES.searchedSite : + // TODO: add Show-Ajax append? test to be sure when this happens... if its just on paging, do we want to track that? + // TODO: what about search-suggestion flyout? probably not supportable + // TODO: be sure to check for 0 result searches, filtering on both search results and PLPs, re-sorts, etc and get clarity on requirements + parms = parms.split('|'); + dataObj = searchedSiteData.getData(parms[0], parms[1]); // parms: search phrase, result count + break; + } + serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, action); + // TODO: need to do anything here with the service call result, or handle all errs etc within trackEvent? otherwise no need to assign to a var / return a value } - serviceCallResult = klaviyoUtils.trackEvent(exchangeID, dataObj, action); - // TODO: need to do anything here with the service call result, or handle all errs etc within trackEvent? otherwise no need to assign to a var / return a value + } else { + // no klaviyo ID, check for SFCC profile and ID off that if extant + res.viewData.klid = klaviyoUtils.getProfileInfo(); } } diff --git a/cartridges/int_klaviyo_sfra/cartridge/controllers/Page.js b/cartridges/int_klaviyo_sfra/cartridge/controllers/Page.js deleted file mode 100644 index 18cc749a..00000000 --- a/cartridges/int_klaviyo_sfra/cartridge/controllers/Page.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -var server = require('server'); -var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); - -server.extend(module.superModule); - -server.append('Show', function (req, res, next) { - if(klaviyoUtils.klaviyoEnabled && !klaviyoUtils.getKlaviyoExchangeID()){ - res.viewData.klid = klaviyoUtils.getProfileInfo(); - } - next(); -}); - - -module.exports = server.exports(); \ No newline at end of file diff --git a/cartridges/int_klaviyo_sfra/cartridge/controllers/Product.js b/cartridges/int_klaviyo_sfra/cartridge/controllers/Product.js deleted file mode 100644 index cab8a8b0..00000000 --- a/cartridges/int_klaviyo_sfra/cartridge/controllers/Product.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -var server = require('server'); -var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); - -server.extend(module.superModule); - -// TODO: willing to bet this doesn't work with cache on!!! have to shift to remote include Klaviyo.js. Same for Search, check others! - -server.append('Show', function (req, res, next) { - if(klaviyoUtils.klaviyoEnabled && !klaviyoUtils.getKlaviyoExchangeID()){ - res.viewData.klid = klaviyoUtils.getProfileInfo(); - } - next(); -}); - - -module.exports = server.exports(); \ No newline at end of file diff --git a/cartridges/int_klaviyo_sfra/cartridge/controllers/Search.js b/cartridges/int_klaviyo_sfra/cartridge/controllers/Search.js deleted file mode 100644 index 18cc749a..00000000 --- a/cartridges/int_klaviyo_sfra/cartridge/controllers/Search.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -var server = require('server'); -var klaviyoUtils = require('*/cartridge/scripts/klaviyo/utils'); - -server.extend(module.superModule); - -server.append('Show', function (req, res, next) { - if(klaviyoUtils.klaviyoEnabled && !klaviyoUtils.getKlaviyoExchangeID()){ - res.viewData.klid = klaviyoUtils.getProfileInfo(); - } - next(); -}); - - -module.exports = server.exports(); \ No newline at end of file diff --git a/cartridges/int_klaviyo_sfra/cartridge/templates/default/klaviyo/klaviyoFooter.isml b/cartridges/int_klaviyo_sfra/cartridge/templates/default/klaviyo/klaviyoFooter.isml index bcb7fd92..2770844d 100644 --- a/cartridges/int_klaviyo_sfra/cartridge/templates/default/klaviyo/klaviyoFooter.isml +++ b/cartridges/int_klaviyo_sfra/cartridge/templates/default/klaviyo/klaviyoFooter.isml @@ -15,11 +15,21 @@ FOOBAR // klaviyo object loader - provided by klaviyo !function(){if(!window.klaviyo){window._klOnsite=window._klOnsite||[];try{window.klaviyo=new Proxy({},{get:function(n,i){return"push"===i?function(){var n;(n=window._klOnsite).push.apply(n,arguments)}:function(){for(var n=arguments.length,o=new Array(n),w=0;w - + + + klAction and klParms are used to pass event information to controllers that require events to be tracked. (PDP, PLP and Search) + they are passed as false to pages that only need to try to ID the user off the SFCC Profile (Home, Page, Default) + + + - +