SFCC Cartridge
3DS Recommendations
3DS Integration Guide
10 min
integrating the 3dsecure in the forter cartridge in order to integrate with 3ds recommendations, merchants will need to modify their forterorder ds file to handle a new "recommendation" field in the response body as well as to pass additional 3ds authorization response data in the post authorization request body 1\ updates to api schema once you've confirmed a 3ds integration with your dedicated forter implementations team, they will modify the schema to include additional fields in the request body payment\[0] creditcard object and enable the exposure of a "recommendations" key in the response object returned by forter in the pre authorization call your implementation team will direct you to the relevant api version and you can specify which version you should point to in the cartridge in the cartridges > int forter > cartridge > scripts > init > forterserviceinit js file of your cartridge by modifying the default option 86 in svc addheader("api version", "option 86"); 2\ enable pre auth go to the checkoutservices js file in your forter cartridge the exact path to this file is cartridges > int forter sfra > cartridge > controllers > checkoutservice js open this file and uncomment the following code after the var order = cohelpers createorder(currentbasket); function and before the var handlepaymentresult = cohelpers handlepayments(order, order orderno); function in the file full checkoutservices js file with pre auth '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(); } // add or uncomment the new pre auth code here var argordervalidate = { order order, ordervalidateattemptinput 1 }, fortercontroller = require('int forter/cartridge/controllers/fortervalidate'), forterdecision = fortercontroller validateorder(argordervalidate); // in case if no response from forter, try to call one more time if (forterdecision result === false && forterdecision ordervalidateattemptinput == 2) { var argordervalidate = { order order, ordervalidateattemptinput 2 }, fortercontroller = require('int forter/cartridge/controllers/fortervalidate'), forterdecision = fortercontroller validateorder(argordervalidate); } if (forterdecision jsonresponseoutput processoraction == 'void') { if (!empty(forterdecision placeordererror)) { return {error true, fortererrorcode forterdecision placeordererror}; } else { return {error true}; } } // end of pre auth code var handlepaymentresult = cohelpers handlepayments(order, order orderno); // handles payment authorization if (handlepaymentresult error) { 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) }); } 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 exposing a direct route to an order, without at least encoding the orderid // is a serious pii violation it enables looking up every customers orders, one at a // time res json({ error false, orderid order orderno, ordertoken order ordertoken, continueurl urlutils url('order confirm') tostring() }); return next(); }); module exports = server exports(); 3\ update your business manager the additional code that you have added above to your checkoutservices js file allows your storefront to send order information to the forter endpoint before an authorization call is made to your payment gateway you will also need to update your forter business manager extension to show a customized error message (in cases of decline) for this updated pre authorization flow 4\ handle recommendations you will need to add logic in your forter sfcc cartridge to handle the new, "recommendation" field that will be included in the forter response for orders that require 3ds merchants should make sure to include logic to handle all possible iterations of both the decisions and recommendations within their forter cartridge ( fortervalidate js ) and within their gateway cartridge to ensure that orders are correctly routed after receiving a pre authorization decision and recommendation from forter in the case of 3ds for regulation , the forter pre authorization decision ("action") will be an "approve" along with a populated "recommendations" field in the case of 3ds for risk assessment , the forter pre authorization decision ("action") will be a "decline" along with a populated "recommendations" field in your sfcc cartridge, go to the fortervalidate file, cartridges/int forter sfra/cartridge/scripts/pipelets/forter/fortervalidate js and add logic to handle both the decision and recommendation in cases where the recommendation field is populated with a value other than an empty string ("") possible recommendation values are "verification required 3ds challenge" "" (when forter does not recommend any 3ds or exemption due to clear approve) "request sca exclusion moto" "request sca exclusion prepaid" "recommendation request sca exclusion one leg out" "request sca exclusion one leg out" "request sca exclusion mit" "request sca exemption tra" "recommendation request sca exemption low value" "request sca exemption low value" "request sca exemption corp" "recommendation request sca exemption trusted beneficiary" "request sca exemption trusted beneficiary" the logic should be modified within the validateorder(args) function around line 137 where the current options and logic for handling decisions and response body fields is located example modification validate order(args) if (fresponse actionenum === 'declined' && fresponse recommendation\[0] === undefined) { // modify accordingly if (siteprefs fortercancelorderondecline === true) { fresponse processoraction = 'void'; if (siteprefs fortershowdeclinedpage === true && siteprefs fortercustomdeclinemessage) { resp placeordererror = { code siteprefs fortercustomdeclinemessage }; } } else { fresponse processoraction = 'skipcapture'; } } else if (fresponse actionenum === 'not reviewed') { fresponse processoraction = 'notreviewed'; } else if (fresponse actionenum === 'approved' && fresponse recommendation\[0] == "") { // add logic to handle recommendation field when no rec is given fresponse processoraction = siteprefs forterautoinvoiceonapprove ? 'capture' 'skipcapture'; } else if (fresponse actionenum === 'approved' && fresponse recommendation\[0] != "request sca exemption low value") { //logic to handle/store 3ds rec for exemption fresponse processoraction = siteprefs initiate3dschallenge //were initiate3dschallenge is a custom setting added to your dw site 'dw/system/site' } else if (fresponse actionenum === 'declined' && fresponse recommendation\[0] != "verification required 3ds challenge") { //logic to handle/store 3ds recommendation fresponse processoraction = siteprefs initiate3dschallenge //were initiate3dschallenge is a custom setting added to your dw site 'dw/system/site' } 5\ code modifications to payment gateway cartridge as seen in the above example, you'll need to modify your sfcc site preferences dw > system > site to handle the new "recommendations" field in the response body in the example above, the fucntionality is called initiate3dschallenge this functionality should allow your sfcc platform to accept a recommendation and trigger a 3ds challenge from your payment gateway each gateway has different 3ds challenge requirements and configurations it will be incumbant on each merchant to handle the integration of the recommendation into their gateway and configure the relevant 3ds challenge to a customer based on the recommendation provided by forter 6\ post auth options for a second, synchronous post auth decision, you will need to update the mapping in your forterorder ds file to include the authorization and 3ds response data from your gateway and make sure your fortervalidate js file also handles the second post auth forter decision for an post auth order status call without a new forter decision ("action"), you'll modify the forterupdate js job and authorization response mapping in the corresponding forterpayment() function option 1 post auth validation call after the gateway has successfully initiated 3ds and authorized payment (or rejected payment), the results should be included in the post authorization call to the forter cartridge these fields should be added to the fortercreditcard(auth, cc) function mapping of your forterorder ds file this flow is applicable for merchants who may not be receiving a forter payment recommendation (3ds or psd2) but want to provide forter's models with the payments outcome from their processor example modified fortercreditcard() with 3ds results function fortercreditcard(auth, cc) { var processorresponsecode = ''; var processorresponsetext = ''; var creditcardexpmonth; // format the expiration month from 1 to 01, etc if (cc creditcardexpirationmonth tostring() length === 1) { creditcardexpmonth = '0' + cc creditcardexpirationmonth tostring(); } else { creditcardexpmonth = cc creditcardexpirationmonth tostring(); } this nameoncard = cc creditcardholder; this cardbrand = cc creditcardtype; if (cc creditcardnumber substring(0, 6) indexof(' ') > 1) { this bin = session forms billing creditcardfields cardnumber value substring(0, 6); // session forms billing paymentmethods creditcard number value substring(0, 6); } else { this bin = cc creditcardnumber substring(0, 6); } this lastfourdigits = cc creditcardnumberlastdigits; this expirationmonth = creditcardexpmonth; this expirationyear = cc creditcardexpirationyear tostring(); this verificationresults = {}; this verificationresults avsfullresult = auth ? auth avsresultcode tostring() ''; this verificationresults cvvresult = auth ? auth cvvresultcode tostring() ''; this verificationresults authorizationcode = auth ? auth authcode tostring() ''; // new 3ds response data this verificationresultsauthorizationpolicy = "3dsecure", //can be hardcoded this verificationresults cavvresult = auth body ? replymessage decisionearlyreply rcode, this verificationresults ecivalue auth body ? auth payerauthvalidationreply eciraw, this verificationresults threedsstatus = auth payerauthvalidationreply authenticationmessage || '', this verificationresults threedsstatuscode = auth payerauthvalidationreply authenticationstatuscoderaw || '', this verificationresults threedsstatusreasoncode = auth body status, this liabilityshift = auth payerauthvalidationreply ? true false, //should be a boolean value threedsversion = auth payerauthvalidationrepl auth version this verificationresults threedsinteractionmode = "frictionless", // modify according to 3ds used this verificationresults threedschallengecancelcode = replymessage decisionearlyreply challengecodenumeric || '', this verificationresults authenticationchallengestarttime = new number((order header timestamp/1000) tofixed()); , // should be timestamp as int in unix format this verificationresults authenticationtriggered = auth payerauthvalidationreply ? true false, //should be a boolean value this verificationresults external3dsvendorpayload = {}, // optional this verificationresults external3dsvendorpayloadbeforechallenge = {}, // optional this verificationresults authenticationmethodtype = "three ds", // can be hardcoded if this is the only auth method this verificationresults authorizationprocessedwith3ds = auth payerauthvalidationreply ? true false, //should be a boolean value this verificationresults partyexecuted3ds = "psp" // add relevant vendor(s) // end of new 3ds response data }, this paymentgatewaydata = {}; this paymentgatewaydata gatewayname = 'authorize net'; this paymentgatewaydata gatewaytransactionid = auth ? auth transid tostring() ''; if (auth && auth errors errorcode length > 0) { processorresponsecode = auth errors errorcode tostring(); processorresponsetext = auth errors errortext tostring(); } else if (auth && auth messages code length > 0) { processorresponsecode = auth messages code tostring(); processorresponsetext = auth messages description tostring(); } this verificationresults processorresponsecode = processorresponsecode; this verificationresults processorresponsetext = processorresponsetext; } option 2 post auth order status call step 1 include post auth status update script the example authorize net script below outlines how to update the script that handles credit card processing so that it includes the relevant post authorization and 3ds order status call to forter note that the two default order statuses are "processing" (when the gateway has authorized the payment) "canceled by merchant" (when the gateway/processor has not authorized payment) the code example below applies to all sfra and sg pre auth/status implementations and the full postauthorderstatusupdate() function can be found in fortervalidate js file ( cartridges > int forter sfra > cartridge > scripts > pipelets > forter > fortervalidate js ) var argorderupdate = { ordernumber ordernumber, updateattempt 1 }, fortercall = require(' /cartridge/scripts/pipelets/forter/fortervalidate'), forterdecision = fortercall postauthorderstatusupdate(argorderupdate, "processing"); if (forterdecision result === false && forterdecision updateattempt == option ) { forterdecision updateattempt = option ; fortercall postauthorderstatusupdate(argorderupdate, "processing"); } step 2 customize forterupdate js file after uncommenting the pre auth code checkoutservices js file, customize the forterupdate js file to include your gateway's authorization and 3ds response in the status call to forter the exact path to the forterupdate js file is cartridges > int forter sfra > cartridge > scripts > lib > forter > dto > forterupdate js ) customize the forterpayment(payment, order) function to map your specific gateway authorization response example fortercreditcard() auth function ```javascript function fortercreditcard(auth, cc) { var processorresponsecode = ''; var processorresponsetext = ''; var creditcardexpmonth; // format the expiration month from 1 to 01, etc if (cc creditcardexpirationmonth tostring() length === 1) { creditcardexpmonth = '0' + cc creditcardexpirationmonth tostring(); } else { creditcardexpmonth = cc creditcardexpirationmonth tostring(); } this nameoncard = cc creditcardholder; this cardbrand = cc creditcardtype; if (cc creditcardnumber substring(0, 6) indexof(' ') > 1) { this bin = session forms billing creditcardfields cardnumber value substring(0, 6); // session forms billing paymentmethods creditcard number value substring(0, 6); } else { this bin = cc creditcardnumber substring(0, 6); } this lastfourdigits = cc creditcardnumberlastdigits; this expirationmonth = creditcardexpmonth; this expirationyear = cc creditcardexpirationyear tostring(); this verificationresults = {}; // new 3ds response data this verificationresultsauthorizationpolicy = "3dsecure", //can be hardcoded this verificationresults cavvresult = auth body ? replymessage decisionearlyreply rcode, this verificationresults ecivalue auth body ? auth payerauthvalidationreply eciraw, this threedsstatus = auth payerauthvalidationreply authenticationmessage || '', this threedsstatuscode = auth payerauthvalidationreply authenticationstatuscoderaw || '', this threedsstatusreasoncode = auth body status, this liabilityshift = auth payerauthvalidationreply ? true false, //should be a boolean value this threedsversion = auth payerauthvalidationrepl auth version this threedsinteractionmode = "frictionless", // modify according to 3ds used this threedschallengecancelcode = replymessage decisionearlyreply challengecodenumeric || '', this authenticationchallengestarttime = new number((order header timestamp/1000) tofixed()); , // should be timestamp as int in unix format this authenticationtriggered = auth payerauthvalidationreply ? true false, //should be a boolean value this external3dsvendorpayload = {}, // optional this external3dsvendorpayloadbeforechallenge = {}, // optional this authenticationmethodtype = "three ds", // can be hardcoded if this is the only auth method this authorizationprocessedwith3ds = auth payerauthvalidationreply ? true false, //should be a boolean value this partyexecuted3ds = "psp" this verificationresults avsfullresult = auth ? auth avsresultcode tostring() ''; this verificationresults cvvresult = auth ? auth cvvresultcode tostring() ''; this verificationresults authorizationcode = auth ? auth authcode tostring() ''; this paymentgatewaydata = {}; this paymentgatewaydata gatewayname = 'authorize net'; this paymentgatewaydata gatewaytransactionid = auth ? auth transid tostring() ''; if (auth && auth errors errorcode length > 0) { processorresponsecode = auth errors errorcode tostring(); processorresponsetext = auth errors errortext tostring(); } else if (auth && auth messages code length > 0) { processorresponsecode = auth messages code tostring(); processorresponsetext = auth messages description tostring(); } this verificationresults processorresponsecode = processorresponsecode; this verificationresults processorresponsetext = processorresponsetext; } };