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
});