---
updatedAt: 2025-11-13T14:37:20.000Z
agentTools:
  siteIndex: https://docs.affirm.com/llms.txt
  projectIndex: https://docs.affirm.com/developers/v1.0-Global-Integration/llms.txt
---

# 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 into three parts:

* Templates
* Controllers
* Client-side JavaScript.

***

## Templates (SFRA)

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

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

```html htmlHead.isml
<isinclude template="affirm/affirmHeaderMF" />
```

![](https://files.readme.io/1dc6ed3-sfra_1_htmlHead.png "sfra_1_htmlHead.png")

***

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

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

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

![](https://files.readme.io/f129223-sfra_2_scripts.png "sfra_2_scripts.png")

***

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

Include the `affirmModule` template after the last line.

```html modules.isml
<isinclude template="util/affirmModule" />
```

![](https://files.readme.io/ca5f3fe-sfra_3_modules.png "sfra_3_modules.png")

***

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

Place promo message below where the cart total is being displayed.

```html cart.isml
<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)}" />
```

![](https://files.readme.io/8640b0a-sfra_4_cart.png "sfra_4_cart.png")

***

### 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`
* `setQuickView` in the product folder.

Place promo messages in relevant locations where price is displayed.

#### setDetails.isml

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

![](https://files.readme.io/b19c280-sfra_5_product-setDetails.png "sfra_5_product-setDetails.png")

#### bundleDetails.isml

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

![](https://files.readme.io/7e5543d-sfra_5_product-bundleDetails.png "sfra_5_product-bundleDetails.png")

#### productDetails.isml

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

![](https://files.readme.io/6874cc9-sfra_5_product-productDetails.png "sfra_5_product-productDetails.png")

#### quickView\.isml & setQuickView\.isml

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

![](https://files.readme.io/f854d15-sfra_5_product-quickView.png "sfra_5_product-quickView.png")

***

### Step 6: product/productTile.isml

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

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

![](https://files.readme.io/f3617cd-sfra_6_product-productTile.png "sfra_6_product-productTile.png")

***

### 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.

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

![](https://files.readme.io/bfdb512-sfra_7_billing-xml.png "sfra_7_billing-xml.png")

> 🚧 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.

```html billingSummary.isml
<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>
```

![](https://files.readme.io/c8e9f7f-sfra_8_checkout-billingSummary.png "sfra_8_checkout-billingSummary.png")

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

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

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

![](https://files.readme.io/8ee0462-sfra_8_checkout-paymentOptions.png "sfra_8_checkout-paymentOptions.png")

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

Include the `affirmpaymethodli` template.

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

![](https://files.readme.io/6a85e90-sfra_8_checkout-paymentOptionsTabs.png "sfra_8_checkout-paymentOptionsTabs.png")

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

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

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

![](https://files.readme.io/32d5b7d-sfra_8_checkout-paymentOptionsContent.png "sfra_8_checkout-paymentOptionsContent.png")

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

Include Affirm payment option in the summary.

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

![](https://files.readme.io/78e4f5d-sfra_8_checkout-paymentOptionsSummary.png "sfra_8_checkout-paymentOptionsSummary.png")

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

Place affirm tracking script at the bottom of the order confirmation page before the closing \</isdecorate> tag.

```html confirmation.isml
<isinclude url="${URLUtils.http('Affirm-Tracking', 'orderId', pdict.order.orderNumber)}" />
```

![](https://files.readme.io/c351c32-sfra_8_checkout-confirmation.png "sfra_8_checkout-confirmation.png")

***

## 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](https://www.npmjs.com/package/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](https://documentation.b2c.commercecloud.salesforce.com/DOC3/topic/com.demandware.dochelp/SFRA/BuildingSFRA.html).

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

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

```javascript
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>';
   }
```

![](https://files.readme.io/f5d52a3-sfra_js_1_billing-1.png "sfra_js_1_billing-1.png")

> 🚧 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.

![](https://files.readme.io/4057cbf-sfra_js_1_billing-2.png "sfra_js_1_billing-2.png")

***

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

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

```javascript checkout.js
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();
		}
	});
}
```

![](https://files.readme.io/d36da71-sfra_js_2_checkout-1.png "sfra_js_2_checkout-1.png")

Modify the block executed when stage is ‘placeOrder’ as shown below:

```javascript
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;
}
```

![](https://files.readme.io/16e4ea0-sfra_js_2_checkout-2.png "sfra_js_2_checkout-2.png")

***

### Step 3: js/cart/cart.js

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

```javascript
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();
```

![](https://files.readme.io/f1e168b-sfra_js_3_cart.png "sfra_js_3_cart.png")

***

### Step 4: js/search/search.js

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

```javascript
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.

# Sibling pages

* [Installation](https://docs.affirm.com/developers/v1.0-Global-Integration/docs/sfcc-installation.md)
* [Configuring Affirm for SFCC](https://docs.affirm.com/developers/v1.0-Global-Integration/docs/configuring-affirm-for-sfcc.md)
* [Controllers & Pipelines Cartridge Installation](https://docs.affirm.com/developers/v1.0-Global-Integration/docs/controllers-pipelines-cartridge-installation.md)
* [Transaction Management with Jobs](https://docs.affirm.com/developers/v1.0-Global-Integration/docs/transaction-management-with-jobs.md)
* [SFCC FAQ](https://docs.affirm.com/developers/v1.0-Global-Integration/docs/sfcc-faq.md)