Increasing Your Revenue with Abandoned Cart Feature


salesforce commerce cloud
      
          const CustomObjectMgr = require('dw/object/CustomObjectMgr');
const Transaction = require('dw/system/Transaction');
const Logger = require('dw/system/Logger');
const Collections = require('*/cartridge/scripts/util/collections');
const BasketMgr = require('dw/order/BasketMgr');
 
function createNewObject(email, currentBasket) {
  const products = Collections.map(currentBasket.allProductLineItems, function (item) {
    return {
      productID: item.productID,
      color: item.product.custom.refinementColor.displayValue,
      price: item.priceValue,
      size: item.product.custom.size,
      name: item.productName,
      quantity: item.quantityValue
    }
  });
  const ID = currentBasket.UUID + '_' + Date.now();
 
  try {
    Transaction.wrap(function () {
      let co = CustomObjectMgr.createCustomObject('abandonedCart', ID);
      co.custom.customerEmail = email;
      co.custom.cartInfo = JSON.stringify(products, null, 4);
      co.custom.registeredCustomer = customer.registered;
      co.custom.totalPrice = currentBasket.adjustedMerchandizeTotalPrice.value;
    });
    session.custom.lastBasketID = currentBasket.UUID;
    session.custom.abandonedCartId = ID;
  } catch (e) {
    Logger.error('{0}', e.message);
  }
}
 
function deleteObject(ID) {
  try {
    Transaction.wrap(function () {
      const co = CustomObjectMgr.getCustomObject('abandonedCart', ID);
      CustomObjectMgr.remove(co);
    });
    delete session.custom.lastBasketID;
    delete session.custom.abandonedCartId;
  } catch (e) {
    Logger.error('{0}', e.message);
  }
}
 
function updateCartInfo(ID) {
  const currentBasket = BasketMgr.getCurrentBasket();
 
  const products = Collections.map(currentBasket.allProductLineItems, function (item) {
    return {
      productID: item.productID,
      color: item.product.custom.refinementColor.displayValue,
      price: item.priceValue,
      size: item.product.custom.size,
      name: item.productName,
      quantity: item.quantityValue
    }
});
 
  try {
    Transaction.wrap(function () {
      let co = CustomObjectMgr.getCustomObject('abandonedCart', ID);
      co.custom.cartInfo = JSON.stringify(products, null, 4);
      co.custom.totalPrice = currentBasket.adjustedMerchandizeTotalPrice.value;
    });
  } catch (e) {
    Logger.error('{0}', e.message);
  }
}
 
function updateEmail(ID, newEmail) {
  try {
    Transaction.wrap(function () {
      const co = CustomObjectMgr.getCustomObject('abandonedCart', ID);
      co.custom.customerEmail = newEmail;
    });
  } catch (e) {
    Logger.error('{0}', e.message);
  }
}
 
module.exports = {
  createNewObject: createNewObject,
  deleteObject: deleteObject,
  updateCartInfo: updateCartInfo,
  updateEmail: updateEmail
};
        
      
          server.append('SubmitCustomer', server.middleware.https, function (req, res, next) {
  if (Site.current.getCustomPreferenceValue('abandonedCartEnabled')) {
    const BasketMgr = require('dw/order/BasketMgr');
    const currentBasket = BasketMgr.getCurrentBasket();
 
    if (session.custom.lastBasketID === currentBasket.UUID) {
      AbandonedCartHelpers.updateEmail(session.custom.abandonedCartId, res.viewData.customer.email.value);
      return next();
    }
    if (currentBasket) AbandonedCartHelpers.createNewObject(res.viewData.customer.email.value, currentBasket);
  }
 
  next();
});
        
      
          server.append('AddProduct', function (req, res, next) {
  if (res.viewData.error) {
    return next();
  }
 
  if (Site.current.getCustomPreferenceValue('abandonedCartEnabled')) {
    const BasketMgr = require('dw/order/BasketMgr');
    const currentBasket = BasketMgr.getCurrentBasket();
 
    if (customer.registered && currentBasket && session.custom.lastBasketID !== currentBasket.UUID) {
      AbandonedCartHelpers.createNewObject(customer.profile.email, currentBasket);
      return next();
    }
 
    if (session.custom.abandonedCartId) {
      AbandonedCartHelpers.updateCartInfo(session.custom.abandonedCartId);
    }
  }
  next();
});
 
server.append('RemoveProductLineItem', function (req, res, next) {
  if (Site.current.getCustomPreferenceValue('abandonedCartEnabled') && session.custom.abandonedCartId && res.viewData.basket) {
    if (res.viewData.basket.numItems === 0) {
      AbandonedCartHelpers.deleteObject(session.custom.abandonedCartId);
      return next();
    }
 
    AbandonedCartHelpers.updateCartInfo(session.custom.abandonedCartId);
  }
  next();
});

server.append('UpdateQuantity', function (req, res, next) {
  if (Site.current.getCustomPreferenceValue('abandonedCartEnabled') && session.custom.abandonedCartId && !res.viewData.valid.error) {
    AbandonedCartHelpers.updateCartInfo(session.custom.abandonedCartId);
  }
  next();
});
        
      
          server.append('PlaceOrder', server.middleware.https, function (req, res, next) {
  if (Site.current.getCustomPreferenceValue('abandonedCartEnabled') && !res.viewData.error) {
    AbandonedCartHelpers.deleteObject(session.custom.abandonedCartId);
  }
  next();
});
        
      
          "step-types": {
    "script-module-step": [
      {
        "@type-id": "custom.HandlingEmailsAbandonedCart",
        "@supports-parallel-execution": "true",
        "@supports-site-context": "true",
        "@supports-organization-context": "false",
        "description": "Send an email about abandoned cart to customers",
        "module": "int_practice/cartridge/scripts/steps/HandleEmailsAbandonedCart.js",
        "function": "HandleEmails",
        "timeout-in-seconds": "900",
        "parameters": {
          "parameter": [
            {
              "@name": "days",
              "@type": "string",
              "description": "Number of days that passed since the cart object was created",
              "@required": "true"
            }
          ]
        },
        "status-codes": {
          "status": [
            {
              "@code": "ERROR",
              "description": "Used when the step failed with an error."
            },
            {
              "@code": "OK",
              "description": "Used when the step executed ok."
            }
          ]
        }
      }
    ]
  }
}
        
      
          const Status = require('dw/system/Status');
const Logger = require('dw/system/Logger');
const CustomObjectMgr = require('dw/object/CustomObjectMgr');
const ProductMgr = require('dw/catalog/ProductMgr');
const URLUtils = require('dw/web/URLUtils');
const EmailHelpers = require('*/cartridge/scripts/helpers/emailHelpers');
const Site = require('dw/system/Site');
const Resource = require('dw/web/Resource');
 
function HandleEmails(parameters) {
  try {
    const date = new Date();
    date.setDate(date.getDate() - parameters.days);
    const customObjects = CustomObjectMgr.queryCustomObjects("abandonedCart", "creationDate <= {0}", null, date);
    const emailObj = {
      subject: Resource.msg('abandonedcart.email.subject', 'abandonedCart', null),
      from: Site.getCurrent().getCustomPreferenceValue('customerServiceEmail'),
      type: EmailHelpers.emailTypes.abandonedCart
    };
    const onlyAvailable = parameters.onlyAvailable;
   
    while (customObjects.hasNext()) {
      let co = customObjects.next();
      let cart = JSON.parse(co.custom.cartInfo);
      let totalPrice = 0;
      const objectForEmail = {
        cart: onlyAvailable ? cart.filter(function (item) {
          const product = ProductMgr.getProduct(item.productID);
          if (product && product.getAvailabilityModel().isInStock()) {
            totalPrice += item.price;
            return item;
          }
        }) : cart,
        totalPrice: onlyAvailable ? totalPrice : co.custom.totalPrice.toString(),
        url: co.custom.registeredCustomer ?
        URLUtils.https('Cart-AbandonedCartRegistered', 'abanodenCartObjectId', co.custom.abandonedCartId) :
        URLUtils.https('Checkout-Begin', 'abanodenCartObjectId', co.custom.abandonedCartId)
      };
      emailObj.to = co.custom.customerEmail;
      if (!empty(objectForEmail.cart)) EmailHelpers.sendEmail(emailObj, 'account/cart/abandonedCart', objectForEmail);
    }
  } catch (e) {
    Logger.error('{0}', e.message);
    return new Status(Status.ERROR, "ERROR", "Emails are not sent!");
  }
  return new Status(Status.OK, "OK", "Emails sent successfully");
}
 
module.exports = {
  HandleEmails: HandleEmails
};
        
      
          abandonedcart.email.subject=You forgot something ?
        
      
          module.exports = Object.assign({}, module.superModule, {
  emailTypes: {
    registration: 1,
    passwordReset: 2,
    passwordChanged: 3,
    orderConfirmation: 4,
    accountLocked: 5,
    accountEdited: 6,
    abandonedCart: 7
  }
});
        
      
          <body>
  <h1> You forgot something ? </h1>
  <isloop items="${pdict.cart}" var="product">
    <span><strong>${product.name}</strong></span>
 
    <p> Color : ${product.color}</p>
    <p> Size : ${product.size}</p>
    <p> Qty : ${product.quantity}</p>
    <p> Price : ${product.price}</p>
    <hr />
  </isloop>
  <p>Total Price: ${pdict.totalPrice}</p>
  <a href="${pdict.url}">Click here to continue to cart</a>
</body>
        
      
          server.get('AbandonedCartRegistered', server.middleware.https, function (req, res, next) {
  const objectId = req.querystring.objectId;
 
  if (!customer.registered) {
    session.custom.customObjectId = objectId;
    res.redirect(URLUtils.url('Login-Show', 'abandonedCartLogin', 'true'));
    return next();
  }
  res.redirect(URLUtils.url('Checkout-Begin', 'abanodenCartObjectId', objectId)
);
  return next();
});
        
      
          server.append('Show',
  server.middleware.https,
  userLoggedIn.validateLoggedIn,
  consentTracking.consent,
  function (req, res, next) {
 
    const URLUtils = require('dw/web/URLUtils');
    const target = req.querystring.rurl || 1;
    if (req.querystring.abandonedCartLogin) {
        res.setViewData({
            actionUrl: URLUtils.url('Account-Login', 'rurl', target),
            abandonedCartLogin : req.querystring.abandonedCartLogin,
            customObjectId: session.custom.customObjectId
        });
        return next();
    }
 
    next();
  });
        
      
          server.prepend('Begin', server.middleware.https, consentTracking.consent, csrfProtection.generateToken, function (req, res, next) {
    const objectId = req.querystring.abanodenCartObjectId;
    if (!empty(objectId)) {
      CartHelpers.restoreBasket(objectId);
      return next();
    }
    next();
});
        
      
          const assign = require('server/assign');
const BasketMgr = require('dw/order/BasketMgr');
const Transaction = require('dw/system/Transaction');
const CustomObjectMgr = require('dw/object/CustomObjectMgr');
 
function restoreBasket(customObjectId) {
  const currentBasket = BasketMgr.getCurrentBasket();
  const object = CustomObjectMgr.getCustomObject("abandonedCart", customObjectId);
  if (currentBasket && object) {
    const cart = JSON.parse(object.custom.cartInfo);
    cart.forEach(function (item) {
      Transaction.wrap(function () {
        module.superModule.addProductToCart(
          currentBasket,
          item.productID,
          item.quantity,
          [],
          []
        );
      });
    })
  }
}
 
module.exports = assign(module.superModule, {
  restoreBasket: restoreBasket
});
        
blog author

Milan Mastilović

Software Developer