MFRA Integration
Accounts MFRA Integration
Accounts SFRA
This section describes how to integrate the Forter cartridge based on the SFRA/MFRA structure. The section provides a list of all affected controllers. Integration of the cartridge into storefront application does not imply modifications of the core cartridge. The standard SFRA/MFRA approach of overwriting files should be used instead.
In cases where the SFCC storefront application has same controllers or templates extended or replaced,all mentioned code modifications must be added to the top level cartridge of the storefront application.
1) Account.js file
Include the following code changes within the Account.js file in the int_forter_mfra section. The exact path to account.js is: Cartridges > int_forter_mfra > cartridge > controllers > Account.js
CUSTOMER_LOGIN (replaces the ‘Login’ with next code include)
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_LOGIN
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);CUSTOMER_CREATE (replaces the `SubmitRegistration’ with next code include):
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_CREATE
}
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);CUSTOMER_PROFILE_UPDATE (replaces the ‘SaveProfile’ with next code include):
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_PROFILE_UPDATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);Full Account.js (including overriding code changes)
'use strict';
// Local Modules
var server = require('server');
server.extend(module.superModule);
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
server.replace(
'Login',
server.middleware.https,
csrfProtection.validateAjaxRequest,
function (req, res, next) {
var Transaction = require('dw/system/Transaction');
var CustomerMgr = require('dw/customer/CustomerMgr');
var Resource = require('dw/web/Resource');
var URLUtils = require('dw/web/URLUtils');
var email = req.form.loginEmail;
var password = req.form.loginPassword;
var rememberMe = req.form.loginRememberMe
? (!!req.form.loginRememberMe)
: false;
var authenticatedCustomer;
var checkoutLogin = req.querystring.checkoutLogin;
Transaction.wrap(function () {
authenticatedCustomer = CustomerMgr.loginCustomer(email, password, rememberMe);
});
if (authenticatedCustomer && authenticatedCustomer.authenticated) {
// *************** add the replacement 'Login' code: ******************
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_LOGIN
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);
// ****************** end of included 'Login' code ******************
res.setViewData({ authenticatedCustomer: authenticatedCustomer });
res.json({
success: true,
redirectUrl: checkoutLogin
? URLUtils.url('Checkout-Begin').toString()
: URLUtils.url('Account-Show').toString()
});
} else {
res.json({ error: [Resource.msg('error.message.login.form', 'login', null)] });
}
return next();
});
server.replace(
'SubmitRegistration',
server.middleware.https,
csrfProtection.validateAjaxRequest,
function (req, res, next) {
var CustomerMgr = require('dw/customer/CustomerMgr');
var Resource = require('dw/web/Resource');
var formErrors = require('*/cartridge/scripts/formErrors');
var registrationForm = server.forms.getForm('profile');
// form validation
if (registrationForm.customer.email.value.toLowerCase()
!== registrationForm.customer.emailconfirm.value.toLowerCase()
) {
registrationForm.customer.email.valid = false;
registrationForm.customer.emailconfirm.valid = false;
registrationForm.customer.emailconfirm.error =
Resource.msg('error.message.mismatch.email', 'forms', null);
registrationForm.valid = false;
}
if (registrationForm.login.password.value
!== registrationForm.login.passwordconfirm.value
) {
registrationForm.login.password.valid = false;
registrationForm.login.passwordconfirm.valid = false;
registrationForm.login.passwordconfirm.error =
Resource.msg('error.message.mismatch.password', 'forms', null);
registrationForm.valid = false;
}
if (!CustomerMgr.isAcceptablePassword(registrationForm.login.password.value)) {
registrationForm.login.password.valid = false;
registrationForm.login.passwordconfirm.valid = false;
registrationForm.login.passwordconfirm.error =
Resource.msg('error.message.password.constraints.not.matched', 'forms', null);
registrationForm.valid = false;
}
// setting variables for the BeforeComplete function
var registrationFormObj = {
firstName: registrationForm.customer.firstname.value,
lastName: registrationForm.customer.lastname.value,
phone: registrationForm.customer.phone.value,
email: registrationForm.customer.email.value,
emailConfirm: registrationForm.customer.emailconfirm.value,
password: registrationForm.login.password.value,
passwordConfirm: registrationForm.login.passwordconfirm.value,
validForm: registrationForm.valid,
form: registrationForm
};
if (registrationForm.valid) {
res.setViewData(registrationFormObj);
this.on('route:BeforeComplete', function (req, res) { // eslint-disable-line no-shadow
var Transaction = require('dw/system/Transaction');
var URLUtils = require('dw/web/URLUtils');
// getting variables for the BeforeComplete function
var registrationForm = res.getViewData(); // eslint-disable-line
if (registrationForm.validForm) {
var login = registrationForm.email;
var password = registrationForm.password;
var authenticatedCustomer;
// attempt to create a new user and log that user in.
try {
Transaction.wrap(function () {
var newCustomer = CustomerMgr.createCustomer(login, password);
if (newCustomer) {
// assign values to the profile
var newCustomerProfile = newCustomer.getProfile();
authenticatedCustomer =
CustomerMgr.loginCustomer(login, password, false);
newCustomerProfile.firstName = registrationForm.firstName;
newCustomerProfile.lastName = registrationForm.lastName;
newCustomerProfile.phoneHome = registrationForm.phone;
newCustomerProfile.email = registrationForm.email;
}
if (authenticatedCustomer === undefined) {
registrationForm.validForm = false;
registrationForm.form.customer.email.valid = false;
registrationForm.form.customer.emailconfirm.valid = false;
}
});
} catch (e) {
registrationForm.validForm = false;
registrationForm.form.customer.email.valid = false;
registrationForm.form.customer.email.error =
Resource.msg('error.message.username.invalid', 'forms', null);
}
}
delete registrationForm.password;
delete registrationForm.passwordConfirm;
formErrors.removeFormValues(registrationForm.form);
if (registrationForm.validForm) {
// *************** add the replacement 'CUSTOMER_CREATE' code: ******************
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_CREATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);
// *************** add the replacement 'Login' code: ******************
res.setViewData({ authenticatedCustomer: authenticatedCustomer });
res.json({
success: true,
redirectUrl: URLUtils.url('Account-Show',
'registration', 'submitted'
).toString()
});
} else {
res.json({
fields: formErrors.getFormErrors(registrationForm)
});
}
});
} else {
res.json({
fields: formErrors.getFormErrors(registrationForm)
});
}
return next();
});
server.replace(
'SaveProfile',
server.middleware.https,
csrfProtection.validateAjaxRequest,
function (req, res, next) {
var Transaction = require('dw/system/Transaction');
var CustomerMgr = require('dw/customer/CustomerMgr');
var Resource = require('dw/web/Resource');
var URLUtils = require('dw/web/URLUtils');
var formErrors = require('*/cartridge/scripts/formErrors');
var profileForm = server.forms.getForm('profile');
// form validation
if (profileForm.customer.email.value.toLowerCase()
!== profileForm.customer.emailconfirm.value.toLowerCase()) {
profileForm.valid = false;
profileForm.customer.email.valid = false;
profileForm.customer.emailconfirm.valid = false;
profileForm.customer.emailconfirm.error =
Resource.msg('error.message.mismatch.email', 'forms', null);
}
var result = {
firstName: profileForm.customer.firstname.value,
lastName: profileForm.customer.lastname.value,
phone: profileForm.customer.phone.value,
email: profileForm.customer.email.value,
confirmEmail: profileForm.customer.emailconfirm.value,
password: profileForm.login.password.value,
profileForm: profileForm
};
if (profileForm.valid) {
res.setViewData(result);
this.on('route:BeforeComplete', function (req, res) { // eslint-disable-line no-shadow
var formInfo = res.getViewData();
var customer = CustomerMgr.getCustomerByCustomerNumber(
req.currentCustomer.profile.customerNo
);
var profile = customer.getProfile();
var customerLogin;
var status;
Transaction.wrap(function () {
status = profile.credentials.setPassword(
formInfo.password,
formInfo.password,
true
);
if (status.error) {
formInfo.profileForm.login.password.valid = false;
formInfo.profileForm.login.password.error =
Resource.msg('error.message.currentpasswordnomatch', 'forms', null);
} else {
customerLogin = profile.credentials.setLogin(
formInfo.email,
formInfo.password
);
}
});
delete formInfo.password;
delete formInfo.confirmEmail;
if (customerLogin) {
Transaction.wrap(function () {
profile.setFirstName(formInfo.firstName);
profile.setLastName(formInfo.lastName);
profile.setEmail(formInfo.email);
profile.setPhoneHome(formInfo.phone);
});
delete formInfo.profileForm;
delete formInfo.email;
// *************** add the replacement 'CUSTOMER_PROFILE_UPDATE' code: ******************
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_PROFILE_UPDATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);
// *************** end 'CUSTOMER_PROFILE_UPDATE' code: ******************
res.json({
success: true,
redirectUrl: URLUtils.url('Account-Show').toString()
});
} else {
if (!status.error) {
formInfo.profileForm.customer.email.valid = false;
formInfo.profileForm.customer.email.error =
Resource.msg('error.message.username.invalid', 'forms', null);
}
delete formInfo.profileForm;
delete formInfo.email;
res.json({
success: false,
fields: formErrors.getFormErrors(profileForm)
});
}
});
} else {
res.json({
success: false,
fields: formErrors.getFormErrors(profileForm)
});
}
return next();
});
module.exports = server.exports();2) Address.js file
Include the following code changes within the Address.js file in the int_forter_mfra section. The exact path to account.js is: Cartridges > int_forter_mfra > cartridge > controllers > Address.js The full Address.js file is also included below
CUSTOMER_ADDRESS_UPDATE (replaces the 'List' with below code)
include the below code in the `Address.js` file
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_ADDRESS_UPDATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);Full Address.js file
'use strict';
//Local Modules
var server = require('server');
server.extend(module.superModule);
server.append('List', function (req, res, next) {
// *************** add the replacement `CUSTOMER_ADDRESS_UPDATE` code: ******************
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_ADDRESS_UPDATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);
// *************** end of included 'CUSTOMER_ADDRESS_UPDATE` code: ******************
next();
});
module.exports = server.exports();3) Login.js File
Include the following code changes within the Login.js file in the int_forter_mfra section. The exact path to account.js is: Cartridges > int_forter_mfra > cartridge > controllers > Login.js The full Login.js file is also included below:
CUSTOMER_LOGOUT (prepends the ‘Logout’ with next code include)
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_LOGOUT
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);Full Login.Js file
'use strict';
//Local Modules
var server = require('server');
server.extend(module.superModule);
server.prepend('Logout', function (req, res, next) {
// *************** add the replacement `CUSTOMER_LOGOUT` code: ******************
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_LOGOUT
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);
// *************** end `CUSTOMER_LOGOUT` code: ******************
next();
});
module.exports = server.exports();4) PaymentInstruments.js File
Include the below code changes to the PaymentInstrument.js file in the int_forter_mfra section. The exact path to account.js is: Cartridges > int_forter_mfra > cartridge > controllers > PaymentInstrument.js The full PaymentInstrument.js file is also included below:
CUSTOMER_PAYMENT_UPDATE (appends the ‘List’ with next code include)
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_PAYMENT_UPDATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);Full PaymentInstrument.js file
'use strict';
//Local Modules
var server = require('server');
server.extend(module.superModule);
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
/**
* Checks if a credit card is valid or not
* @param {Object} card - plain object with card details
* @param {Object} form - form object
* @returns {boolean} a boolean representing card validation
*/
function verifyCard(card, form) {
var collections = require('*/cartridge/scripts/util/collections');
var Resource = require('dw/web/Resource');
var PaymentMgr = require('dw/order/PaymentMgr');
var PaymentStatusCodes = require('dw/order/PaymentStatusCodes');
var paymentCard = PaymentMgr.getPaymentCard(card.cardType);
var error = false;
var cardNumber = card.cardNumber;
var creditCardStatus;
var formCardNumber = form.cardNumber;
if (paymentCard) {
creditCardStatus = paymentCard.verify(
card.expirationMonth,
card.expirationYear,
cardNumber
);
} else {
formCardNumber.valid = false;
formCardNumber.error =
Resource.msg('error.message.creditnumber.invalid', 'forms', null);
error = true;
}
if (creditCardStatus && creditCardStatus.error) {
collections.forEach(creditCardStatus.items, function (item) {
switch (item.code) {
case PaymentStatusCodes.CREDITCARD_INVALID_CARD_NUMBER:
formCardNumber.valid = false;
formCardNumber.error =
Resource.msg('error.message.creditnumber.invalid', 'forms', null);
error = true;
break;
case PaymentStatusCodes.CREDITCARD_INVALID_EXPIRATION_DATE:
var expirationMonth = form.expirationMonth;
var expirationYear = form.expirationYear;
expirationMonth.valid = false;
expirationMonth.error =
Resource.msg('error.message.creditexpiration.expired', 'forms', null);
expirationYear.valid = false;
error = true;
break;
default:
error = true;
}
});
}
return error;
}
/**
* Creates an object from form values
* @param {Object} paymentForm - form object
* @returns {Object} a plain object of payment instrument
*/
function getDetailsObject(paymentForm) {
return {
name: paymentForm.cardOwner.value,
cardNumber: paymentForm.cardNumber.value,
cardType: paymentForm.cardType.value,
expirationMonth: paymentForm.expirationMonth.value,
expirationYear: paymentForm.expirationYear.value,
paymentForm: paymentForm
};
}
server.append('List', function (req, res, next) {
// *************** add the replacement `CUSTOMER_PAYMENT_UPDATE` code: ******************
var argCustomerUpdate = {
EventType : require('~/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig.CUSTOMER_PAYMENT_UPDATE
},
forterCall = require('~/cartridge/scripts/pipelets/forter/ForterCustomerUpdate');
forterCall.execute(argCustomerUpdate);
// *************** end the replacement `CUSTOMER_PAYMENT_UPDATE` code: ******************
next();
});
server.replace(
'SavePayment',
csrfProtection.validateAjaxRequest,
function (req, res, next) {
var formErrors = require('*/cartridge/scripts/formErrors');
var HookMgr = require('dw/system/HookMgr');
var PaymentMgr = require('dw/order/PaymentMgr');
var dwOrderPaymentInstrument = require('dw/order/PaymentInstrument');
var paymentForm = server.forms.getForm('creditCard');
var result = getDetailsObject(paymentForm);
if (paymentForm.valid && !verifyCard(result, paymentForm)) {
res.setViewData(result);
this.on('route:BeforeComplete', function (req, res) { // eslint-disable-line no-shadow
var URLUtils = require('dw/web/URLUtils');
var CustomerMgr = require('dw/customer/CustomerMgr');
var Transaction = require('dw/system/Transaction');
var formInfo = res.getViewData();
var customer = CustomerMgr.getCustomerByCustomerNumber(
req.currentCustomer.profile.customerNo
);
var wallet = customer.getProfile().getWallet();
Transaction.wrap(function () {
var paymentInstrument = wallet.createPaymentInstrument('CREDIT_CARD');
paymentInstrument.setCreditCardHolder(formInfo.name);
paymentInstrument.setCreditCardNumber(formInfo.cardNumber);
paymentInstrument.setCreditCardType(formInfo.cardType);
paymentInstrument.setCreditCardExpirationMonth(formInfo.expirationMonth);
paymentInstrument.setCreditCardExpirationYear(formInfo.expirationYear);
//The basic random tokenization makes impossible to get a raw CC number for Forter call.
/*
var processor = PaymentMgr.getPaymentMethod(dwOrderPaymentInstrument.METHOD_CREDIT_CARD).getPaymentProcessor();
var token = HookMgr.callHook(
'app.payment.processor.' + processor.ID.toLowerCase(),
'createMockToken'
);
paymentInstrument.setCreditCardToken(token);*/
});
res.json({
success: true,
redirectUrl: URLUtils.url('PaymentInstruments-List').toString()
});
});
} else {
res.json({
success: false,
fields: formErrors.getFormErrors(paymentForm)
});
}
return next();
});
module.exports = server.exports();5) CheckoutServices.js
The CheckoutServices.js file replaces the PlaceOrder with the below code in order to handle the customized error massage configured in the Forter business manager extension:
if (ForterConfig.forterShowDeclinedPage == true && !empty(ForterConfig.forterCustomDeclineMessage)) {
res.json({
error: true,
errorMessage: ForterConfig.forterCustomDeclineMessage
});
}Full CheckoutServices.js file
'use strict';
var server = require('server');
server.extend(module.superModule);
var COHelpers = require('*/cartridge/scripts/checkout/checkoutHelpers');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
var ForterConfig = require('int_forter_mfra/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig;
server.replace('PlaceOrder', server.middleware.https, function (req, res, next) {
var BasketMgr = require('dw/order/BasketMgr');
var HookMgr = require('dw/system/HookMgr');
var OrderMgr = require('dw/order/OrderMgr');
var Resource = require('dw/web/Resource');
var Transaction = require('dw/system/Transaction');
var URLUtils = require('dw/web/URLUtils');
var basketCalculationHelpers = require('*/cartridge/scripts/helpers/basketCalculationHelpers');
var currentBasket = BasketMgr.getCurrentBasket();
if (!currentBasket) {
res.json({
error: true,
cartError: true,
fieldErrors: [],
serverErrors: [],
redirectUrl: URLUtils.url('Cart-Show').toString()
});
return next();
}
if (req.session.privacyCache.get('fraudDetectionStatus')) {
res.json({
error: true,
cartError: true,
redirectUrl: URLUtils.url('Error-ErrorCode', 'err', '01').toString(),
errorMessage: Resource.msg('error.technical', 'checkout', null)
});
return next();
}
var validationBasketStatus = HookMgr.callHook(
'app.validate.basket',
'validateBasket',
currentBasket,
false
);
if (validationBasketStatus.error) {
res.json({
error: true,
errorMessage: validationBasketStatus.message
});
return next();
}
// Check to make sure there is a shipping address
if (currentBasket.defaultShipment.shippingAddress === null) {
res.json({
error: true,
errorStage: {
stage: 'shipping',
step: 'address'
},
errorMessage: Resource.msg('error.no.shipping.address', 'checkout', null)
});
return next();
}
// Check to make sure billing address exists
if (!currentBasket.billingAddress) {
res.json({
error: true,
errorStage: {
stage: 'payment',
step: 'billingAddress'
},
errorMessage: Resource.msg('error.no.billing.address', 'checkout', null)
});
return next();
}
// Calculate the basket
Transaction.wrap(function () {
basketCalculationHelpers.calculateTotals(currentBasket);
});
// Re-validates existing payment instruments
var validPayment = COHelpers.validatePayment(req, currentBasket);
if (validPayment.error) {
res.json({
error: true,
errorStage: {
stage: 'payment',
step: 'paymentInstrument'
},
errorMessage: Resource.msg('error.payment.not.valid', 'checkout', null)
});
return next();
}
// Re-calculate the payments.
var calculatedPaymentTransactionTotal = COHelpers.calculatePaymentTransaction(currentBasket);
if (calculatedPaymentTransactionTotal.error) {
res.json({
error: true,
errorMessage: Resource.msg('error.technical', 'checkout', null)
});
return next();
}
// Creates a new order.
var order = COHelpers.createOrder(currentBasket);
if (!order) {
res.json({
error: true,
errorMessage: Resource.msg('error.technical', 'checkout', null)
});
return next();
}
// Handles payment authorization
var handlePaymentResult = COHelpers.handlePayments(order, order.orderNo);
if (handlePaymentResult.error) {
// *************** include `custome message` code: ******************
if (ForterConfig.forterShowDeclinedPage == true && !empty(ForterConfig.forterCustomDeclineMessage)) {
res.json({
error: true,
errorMessage: ForterConfig.forterCustomDeclineMessage
});
} else {
res.json({
error: true,
errorMessage: Resource.msg('error.technical', 'checkout', null)
});
}
// *************** end `custom message` code: ******************
return next();
}
var fraudDetectionStatus = HookMgr.callHook('app.fraud.detection', 'fraudDetection', currentBasket);
if (fraudDetectionStatus.status === 'fail') {
Transaction.wrap(function () { OrderMgr.failOrder(order); });
// fraud detection failed
req.session.privacyCache.set('fraudDetectionStatus', true);
res.json({
error: true,
cartError: true,
redirectUrl: URLUtils.url('Error-ErrorCode', 'err', fraudDetectionStatus.errorCode).toString(),
errorMessage: Resource.msg('error.technical', 'checkout', null)
});
return next();
}
// Places the order
var placeOrderResult = COHelpers.placeOrder(order, fraudDetectionStatus);
if (placeOrderResult.error) {
res.json({
error: true,
errorMessage: Resource.msg('error.technical', 'checkout', null)
});
return next();
}
COHelpers.sendConfirmationEmail(order, req.locale.id);
// Reset usingMultiShip after successful Order placement
req.session.privacyCache.set('usingMultiShipping', false);
TODO
res.json({
error: false,
orderID: order.orderNo,
orderToken: order.orderToken,
continueUrl: URLUtils.url('Order-Confirm').toString()
});
return next();
});
module.exports = server.exports();ForterConfig.forterCustomDeclineMessage (replaces the ‘PlaceOrder’ with next code include)