SFRA cartridge installation

Overview

This page describes changes that should be made to the site for SFRA storefronts. All of the modifications are stored in the mirrored cartridge structure in int_affirm_sfra/_cartridge for default Storefront Reference Architecture so you can find the exact code there.

Custom modifications to your core cartridge outlined in this guide can be separated to three parts: templates, controllers, and client-side JavaScript.


Templates (SFRA)

Step 1: cartridge/templates/default/common/htmlHead.isml

Include Affirm header script tag after Google Verification tag (or after any other relevant tags and before the htmlHead hook). This tag will populate configuration object and methods to be placed in the html head.

<isinclude template="affirm/affirmHeaderMF" />
960960

Step 2: cartridge/templates/default/common/scripts.isml

Include vcn.js as static file at the bottom of the file.

<isif condition="${dw.system.Site.getCurrent().getCustomPreferenceValue('AffirmOnline')}">
   <script defer type="text/javascript" src="${URLUtils.staticURL('/js/vcn.js')}"></script>
</isif>
960960

Step 3: cartridge/templates/default/components/modules.isml

Include the affirmModule template after the last line.

<isinclude template="util/affirmModule" />
16001600

Step 4: cartridge/templates/default/cart/cart.isml

Place promo message below where cart total is displayed.

<isset name="Basket" value="${require('dw/order/BasketMgr').getCurrentBasket()}" scope="page" />
<isinclude template="util/affirmModule"/>
<isaffirmpromo context="cart" fpname="${require('int_affirm/cartridge/scripts/utils/affirmUtils').getFPNameByBasket(Basket)}" />
16001600

Step 5: cartridge/templates/default/product/

There is a set of recurring snippets that need to be placed in order for the Affirm promo message to be shown on the various product related pages. The list of templates that correspond to the pages include setDetails, productDetails, bundleDetails, quickView and setQuickView in the product folder.

Place promo messages in relevant locations where price is displayed.

setDetails.isml

<isinclude template="util/affirmModule" />
<isaffirmpromo context="pdp" fpname="${require('*/cartridge/scripts/utils/affirmUtils').getFPNameForPDP(product)}" >
16001600

bundleDetails.isml

<isinclude template="util/affirmModule" />
<isaffirmpromo context="pdp" fpname="${require('*/cartridge/scripts/utils/affirmUtils').getFPNameForPDP(product)}" >
15981598

productDetails.isml

<isinclude template="util/affirmModule" />
<isaffirmpromo context="pdp" fpname="${require('*/cartridge/scripts/utils/affirmUtils').getFPNameForPDP(product)}" >
16001600

quickView.isml & setQuickView.isml

<isinclude template="util/affirmModule" />
<isaffirmpromo context="pdp" fpname="${require('*/cartridge/scripts/utils/affirmUtils').getFPNameForPDP(product)}" >
16001600

Step 6: product/productTile.isml

Paste the following code where the Affirm promo message should be shown on the product listing page.

<isinclude template="util/affirmModule"/>
<isaffirmpromo context="plp" fpname="${require('int_affirm/cartridge/scripts/utils/affirmUtils').getFPNameForPLP(product)}" price="${price}"/>
16001600

Step 7: cartridge/forms/default/billing.xml

In Storefront Reference Architecture where the credit card form is hardcoded into the templates you need to manually modify these templates to enable the Affirm payment. Make sure email field is included in the billing form.

<field formid="email" label="profile.email" type="string" mandatory="true" binding="email" max-length="254" missing-error="error.card.info.missing.email" />
16001600

🚧

Important

  • Make sure the corresponding email field is included in default/checkout/billing/billing.isml
  • Older versions of SFRA may include an email field in forms/default/creditCard.xml as well as in the template checkout/billing/creditCardForm.isml in which case the redundant email field in creditCard.xml as well as creditCardForm.isml should be removed.

Step 8: cartridge/templates/default/checkout

Edit the following templates to integrate Affirm payment into the checkout flow.

cartridge/templates/default/checkout/billing/billingSummary.isml

Include a div tag with vcn data.

<isset name="CurrentBasket" value="${require('dw/order/BasketMgr').getCurrentBasket()}" scope="page" />
<isif condition="${CurrentBasket}">
   <isset name="basketCheck" value="${CurrentBasket.getAllProductLineItems().isEmpty()}" scope="page" />
   <isif condition="${!basketCheck}">
      <div 
         data-vcndata='<isprint value="${affirm.basket.getCheckout(CurrentBasket, 1)}" encoding="htmlsinglequote" />'
         data-enabled='<isprint value="${affirm.data.getAffirmVCNStatus() == 'on'}" encoding="htmlsinglequote" />'
         data-affirmselected='<isprint value="${true}" encoding="on" />'
         data-errormessages='<isprint value="${affirm.data.getErrorMessages()}" encoding="htmlsinglequote" />'
         data-updateurl='<isprint value="${dw.web.URLUtils.https("Affirm-Update")}" encoding="htmlsinglequote" />'
         data-errorurl='<isprint value="${dw.web.URLUtils.https("Checkout-Begin")}" encoding="htmlsinglequote" />'
         data-csrfname='<isprint value="${dw.web.CSRFProtection.getTokenName()}" encoding="htmlsinglequote" />'
         data-csrftoken='<isprint value="${dw.web.CSRFProtection.generateToken()}" encoding="htmlsinglequote" />'
         id="vcn-data"></div>
   </isif>
</isif>
16001600

cartridge/templates/default/checkout/billing/paymentOptions.isml

Add a condition attribute within the payment-information div for switching payment methods whether Affirm is applicable.

data-payment-method-id="<isif condition="${require('int_affirm/cartridge/scripts/utils/affirmHelper').IsAffirmApplicable()}">Affirm<iselse>CREDIT_CARD</isif>"
16001600

cartridge/templates/default/checkout/billing/paymentOptionsTabs.isml

Include the affirmpaymethodli template.

<isif condition="${paymentOption.ID === 'Affirm' && require('*/cartridge/scripts/utils/affirmHelper').IsAffirmApplicable()}">
<isinclude template="affirm/affirmpaymethodli" />
</isif>
16001600

cartridge/templates/default/checkout/billing/paymentOptionsContent.isml

Include the payment method input template within the payment options isloop.

<isif condition="${paymentOption.ID === 'Affirm' && require('*/cartridge/scripts/utils/affirmHelper').IsAffirmApplicable()}">
    <isinclude template="affirm/paymentMethodInputMF" />
</isif>
16001600

cartridge/templates/default/checkout/billing/paymentOptionsSummary.isml

Include Affirm payment option in the summary.

<isif condition="${payment.paymentMethod === 'Affirm'}">
  ${Resource.msg('payment.name', 'affirm', null)}
</isif>
16001600

cartridge/templates/default/checkout/confirmation/confirmation.isml

Places affirm tracking script at the bottom of order confirmation page before the closing tag.

<isinclude url="${URLUtils.http('Affirm-Tracking', 'orderId', pdict.order.orderNumber)}" >
16001600

Controllers (SFRA)

πŸ“˜

CheckoutServices Controllers

Checkout endpoints in CheckoutServices.js were replaced to handle Affirm and Credit card payments, which can be found in int_affirm_sfra/cartridge/controllers/.

No custom code is required in controllers apart from the CheckoutServices.js which is included in int_affirm_sfra.


Client-side JavaScript (SFRA)

πŸ‘

Running build scripts

Client-side JavaScript and CSS will need to be compiled before deployment. Update package.json at the root to make sure the value of paths property is referencing the base cartridge. In addition, please make sure the SGMF Scripts package is installed and run the following command after making changes:

sgmf-scripts --compile js

More information on the build commands can be found here.

Step 1: cartridge/client/default/js/checkout/billing.js

Replace the if statement inside the updatePaymentInformation function with the following:

if (order.billing.payment && order.billing.payment.selectedPaymentInstruments
       && order.billing.payment.selectedPaymentInstruments.length > 0 && ($('.payment-information').data('payment-method-id') === 'CREDIT_CARD')) {
       htmlToAppend += '<span>' + order.resources.cardType + ' '
           + order.billing.payment.selectedPaymentInstruments[0].type
           + '</span><div>'
           + order.billing.payment.selectedPaymentInstruments[0].maskedCreditCardNumber
           + '</div><div><span>'
           + order.resources.cardEnding + ' '
           + order.billing.payment.selectedPaymentInstruments[0].expirationMonth
           + '/' + order.billing.payment.selectedPaymentInstruments[0].expirationYear
           + '</span></div>';
   } else {
       htmlToAppend += '<span><div>Affirm</div></span>';
   }
16001600

🚧

Important

Make sure the billing.js includes force clearing security code and credit card number inside the updateBillingAddressFormValues function, which SFRA should include by default.

16001600

Step 2: cartridge/client/default/js/checkout/checkout.js

Add an if statement based on checkout stage inside the updateUrl function.

if (checkoutStages[currentStage] === 'payment') {
    if ($('#affirm-config').data('affirmenabled')) {
        $('.affirm-payment-tab').trigger('click');
    }
} else if (checkoutStages[currentStage] === 'placeOrder') {
    var url = $('#affirm-config').data('affirupdateurl');
    $.spinner().start();
    $.ajax({
        url: url,
        method: 'GET',
        success: function (data) {
            $('#vcn-data').data('vcndata', JSON.parse(data.vcndata));
            $.spinner().stop();
        }
    });
}
16001600

Modify the block executed when stage is β€˜placeOrder’ as shown below:

else if (stage === 'placeOrder') {
    if (($('.payment-summary .js-affirm-payment-description').length <= 0)    // when affirm is not used
        || ($('#affirm-config').data('vcnenabled') && $('#vcn-data').data('vcncomplete'))) {  // or vcn is enabled but complete
        $.ajax({
            url: $('.place-order').data('action'),
            method: 'POST',
            success: function (data) {
            // enable the placeOrder button here
                $('body').trigger('checkout:enableButton', '.next-step-button button');
                if (data.error) {
                    if (data.cartError) {
                        window.location.href = data.redirectUrl;
                        defer.reject();
                    } else {
                        // go to appropriate stage and display error message
                        defer.reject(data);
                    }
                } else {
                    var continueUrl = data.continueUrl;
                    var urlParams = {
                        ID: data.orderID,
                        token: data.orderToken
                    };

                    continueUrl += (continueUrl.indexOf('?') !== -1 ? '&' : '?') +
                    Object.keys(urlParams).map(function (key) {
                        return key + '=' + encodeURIComponent(urlParams[key]);
                    }).join('&');

                    window.location.href = continueUrl;
                    defer.resolve(data);
                }
            },
            error: function () {
            // enable the placeOrder button here
                $('body').trigger('checkout:enableButton', $('.next-step-button button'));
            }
        });
    }
    return defer;
}
16001600

Step 3: js/cart/cart.js

Add recalculation for the Affirm Modal Promo price on re-rendering of order totals.

var totalCalculated = data.totals.grandTotal.substr(1).replace(/,/g, '');
$('.affirm-as-low-as').attr('data-amount', (totalCalculated * 100).toFixed());
var isWithinAffirmLimit = (parseFloat(totalCalculated) >= parseFloat(affirmLimits.min) && parseFloat(totalCalculated) <= parseFloat(affirmLimits.max))
if (isWithinAffirmLimit) {
    $('#js-affirm-checkout-now').show();
} else {
    $('#js-affirm-checkout-now').hide();
  $('.affirm-as-low-as').attr('data-amount', NaN);
}
affirm.ui.refresh();
16001600

Step 4: js/search/search.js

Update showMore method to include affirm.ui.refresh as a success callback as shown below.

showMore: function () {
  // Show more products
  $('.container').on('click', '.show-more button', function (e) {
      e.stopPropagation();
      var showMoreUrl = $(this).data('url');

      e.preventDefault();

      $.spinner().start();
      $(this).trigger('search:showMore', e);
      $.ajax({
          url: showMoreUrl,
          data: { selectedUrl: showMoreUrl },
          method: 'GET',
          success: function (response) {
              $('.grid-footer').replaceWith(response);
              updateSortOptions(response);
              affirm.ui.refresh()
              $.spinner().stop();
          },
          error: function () {
              $.spinner().stop();
          }
      });
  });
},

πŸ‘

Make sure to rebuild client JS after making modifications.


Did this page help you?