Consent Tracking in SFRA
You can implement consent tracking on a storefront based on SFRA. Merchants can track personal information about their shoppers to improve the shopping experience. The merchant can collect and honor shoppersβ consent preferences when they are using the site.
The following example shows one way to implement consent tracking on a storefront adapted from SFRA and is provided for informational purposes only. As always, merchants can consult their own legal department or advisors to review and consent management process.
The implementation uses:
- A content asset for displaying a consent request message to the shopper
- The site-specific preference Tracking to disable tracking by default
- A session-specific flag to allow tracking if the shopper grants consent
Consent Asset for the Consent Request Message
To display a consent message to the shopper, a content
asset whose internal ID is tracking_hint
is used. You can
replace the placeholder text in the content asset with your own
message.
Tracking Preference
The Tracking site preference determines the default tracking behavior. When set to Opt-in, personal information is not tracked by default for all shoppers visiting the site; when not set to Opt-in, personal information is tracked.
To set this preference, select Merchant Tools > site > Site Preferences > Privacy.
The example assumes the Tracking preference is set to Opt-In.
Session Tracking Flag
The example presents a consent request message to the shopper, who can choose to allow tracking. If the shopper allows tracking, it is enabled during the shopperβs session. For more information, see Browser-Based Local Data Storage.
To enable tracking on a session:
-
dw.system.Session.setTrackingAllowed(boolean)
β If the boolean value istrue
, tracking is enabled for the current session; iffalse
, tracking is disabled.
To check the current value of the session's tracking flag:
-
dw.system.Session.isTrackingAllowed()
βtrue
indicates that tracking is enabled;false
, disabled.
Implementation Overview
The sample implementation adds a
<span>
tag to every page in the storefront. The
<span>
tag stores information about the shopperβs consent
choices and makes the information available to client-side code running in the
browser. If the shopper has not yet given consent, the client-side code inspects the
<span>
tag and displays a Tracking Consent window.
The <span>
tag is created by the template
app_storefront_base/cartridge/templates/default/common/consent.isml.
<span class="api-${pdict.consentApi} ${pdict.tracking_consent == null ? '' : 'consented' } tracking-consent"
data-url="${URLUtils.url('ConsentTracking-GetContent', 'cid', 'tracking_hint')}"
data-reject="${URLUtils.url('ConsentTracking-SetSession', 'consent', 'false')}"
data-accept="${URLUtils.url('ConsentTracking-SetSession', 'consent', 'true')}"
data-acceptText="${Resource.msg('button.consentTracking.yes', 'common', null)}"
data-rejectText="${Resource.msg('button.consentTracking.no', 'common', null)}"
data-heading="${Resource.msg('heading.consentTracking.track.consent', 'common', null)}"
></span>
This
template is rendered by the ConsentTracking-Check
route in the
app_storefront_base/cartridge/controllers/ConsentTracking.js
controller.
server.get('Check', consentTracking.consent, function (req, res, next) {
res.render('/common/consent', {
consentApi: Object.prototype.hasOwnProperty.call(req.session.raw, 'setTrackingAllowed')
});
next();
});
This
route is called every time a storefront page is rendered. In the route, the
middleware step consentTracking.consent
is invoked before the
consent.isml template is rendered.
This middleware step is implemented in the server-side script app_storefront_base/cartridge/scripts/middleware/consentTracking.js.
'use strict';
/**
* Middleware to use consent tracking check
* @param {Object} req - Request object
* @param {Object} res - Response object
* @param {Function} next - Next call in the middleware chain
* @returns {void}
*/
function consent(req, res, next) {
var consented = req.session.privacyCache.get('consent');
if (consented === null || consented === undefined) {
req.session.privacyCache.set('consent', null);
} else if (consented === false) {
req.session.privacyCache.set('consent', false);
req.session.raw.setTrackingAllowed(false);
} else if (consented === true) {
req.session.privacyCache.set('consent', true);
req.session.raw.setTrackingAllowed(true);
}
res.setViewData({
tracking_consent: req.session.privacyCache.get('consent')
});
next();
}
module.exports = {
consent: consent
};
It
checks the value of the session's consent
property. Regardless if
the value of the property is either true
or false
,
it calls the setTrackingAllowed()
method on the session to record
the shopperβs choice.
It then adds the value of the consent
property to the response's view data, storing it in the
tracking_consent
property. The
tracking_consent
property is made available to the
consent.isml
template via the pdict
property.
The first time this code is executed in a session, the value of the
session's consent
property is null
, so the
tracking_consent
property is also null. Because the value of
the tracking_consent
property is null
, the first
line in the consent.isml
template:
<span class="api-${pdict.consentApi} ${pdict.tracking_consent == null ? '' : 'consented' } tracking-consent"
Outputs the following HTML:
<span class="api-true tracking-consent" ...
In
this output, the consented
attribute value is absent, and its
absence indicates to the client-side code that the shopper has not yet consented to
tracking. The absence of this attribute causes the client-side code to display the
Tracking Consent window, prompting the shopper to grant or deny consent.
consentTracking.consent
middleware step set the
tracking_consent
property to a non-null value. As a consequence,
the resulting HTML output looks like this code snippet: <span class="api-true consented tracking-consent" ...
In this output, the consented attribute value is present, which indicates to the client-side code that the shopper has either granted or denied access to tracking. Whichever choice the shopper made, it is no longer necessary to display the Tracking Consent window.
Every time the consent.isml template is
rendered, the consentApi
property is passed into the template via
the pdict
property. The value of the consentApi
property is the result of the following call:
Object.prototype.hasOwnProperty.call(req.session.raw, 'setTrackingAllowed')
The
call checks if the session has a setTrackingAllowed
property. If it
does, this call resolves to true
; otherwise,
false
. This call always evaluates to true
for
Salesforce B2C Commerce version 18.4 and later.
The client-side script,
app_storefront_base/cartridge/client/default/js/components/consentTracking.js,
inspects the <span>
tag created by the
consent.isml template.
'use strict';
/**
* Renders a modal window that tracks the shoppers consenting to accepting site
* tracking policy
*/
function showConsentModal() {
var urlContent = $('.tracking-consent').data('url');
var urlAccept = $('.tracking-consent').data('accept');
var urlReject = $('.tracking-consent').data('reject');
var textYes = $('.tracking-consent').data('accepttext');
var textNo = $('.tracking-consent').data('rejecttext');
var textHeader = $('.tracking-consent').data('heading');
var htmlString = '<!-- Modal -->'
+ '<div class="modal show" id="consent-tracking" role="dialog" style="display: block;">'
+ '<div class="modal-dialog">'
+ '<!-- Modal content-->'
+ '<div class="modal-content">'
+ '<div class="modal-header">'
+ textHeader
+ '</div>'
+ '<div class="modal-body"></div>'
+ '<div class="modal-footer">'
+ '<div class="button-wrapper">'
+ '<button class="affirm btn btn-primary" data-url="' + urlAccept + '">'
+ textYes
+ '</button>'
+ '<button class="decline btn btn-primary" data-url="' + urlReject + '">'
+ textNo
+ '</button>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>';
$.spinner().start();
$('body').append(htmlString);
$.ajax({
url: urlContent,
type: 'get',
dataType: 'html',
success: function (response) {
$('.modal-body').html(response);
},
error: function () {
$('#consent-tracking').remove();
}
});
$('#consent-tracking .button-wrapper button').click(function (e) {
e.preventDefault();
var url = $(this).data('url');
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function () {
$('#consent-tracking .modal-body').remove();
$('#consent-tracking').remove();
$.spinner().stop();
},
error: function () {
$('#consent-tracking .modal-body').remove();
$('#consent-tracking').remove();
$.spinner().stop();
}
});
});
}
module.exports = function () {
if ($('.consented').length === 0 && $('.tracking-consent').hasClass('api-true')) {
showConsentModal();
}
if ($('.tracking-consent').hasClass('api-true')) {
$('.tracking-consent').click(function () {
showConsentModal();
});
}
};
This
script constructs a section of HTML, stores it in the variable
htmlString
, and appends it to the <body>
element of the DOM. The script then performs an Ajax call to get the value of the
tracking_hint
content asset. If the call is successful, the
script inserts the value of the content asset into the <div> tag whose class
attribute is modal-body
. If the call fails, the script removes the
entire section of appended HTML from the DOM.
The script then creates an
event listener on both <button>
elements in the appended
HTML. If the shopper clicks either button in the Tracking Consent window, the script
makes a second Ajax call, either enabling or disabling tracking on the session. The
script then removes the Tracking Consent window from the DOM.
Lastly, this
script exports a function whose purpose is to conditionally call the
showConsentModal()
function. The exported function is invoked
in two situations: after each storefront page is fully loaded in the browser window,
and when the shopper clicks Consent to Track in the Edit
Profile form on the My Account page. For more information, see the
app_storefront_base/cartridge/templates/default/account/editProfileForm.isml
template.