Skip to main content

 

Affirm Merchant Help

SFCC - Custom Code

1. Custom Code for SFRA Sites

This section describes changes that should be made to a merchant’s custom cartridge. All of this functionality is implemented in int_affirm_sfra cartridge for default Storefront Reference Architecture so you can find exact code there.

    1. Find and open the below template /common/htmlHead.isml. Find the following code.

Paste code after:

<isinclude template="affirm/affirmheader_mf" />

  2. Find and open template /common/scripts.isml. Paste code at the top of the file:

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

3. Find and open template /cart/cart.isml. Paste the following code in the section where you want to see Affirm promo message. If it should be below the cart then you need to paste the code after <isinclude template="cart/cartTotals" /> line:

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

4. Paste the following code where the Affirm promo message should be shown on product details page. You need to modify setDetails.isml, productDetails.isml, bundleDetails.isml in the product folder:

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

/sfra/cartridge/templates/default/product/setDetails.isml

/sfra/cartridge/templates/default/product/productDetails.isml

5. Paste the following code where the Affirm promo message should be shown on the product listing  page. You need to modify /sfra/cartridge/templates/default/product/productTile.isml

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

6. Since credit card is hardcoded in SFRA you need to manually modify templates to enable the Affirm payment:
Forms folder: billing.xml - added email field in billing level

7. Find and open template: /sfra/cartridge/templates/default/components/modules.isml.

Paste code after the last line:

<isinclude template="util/affirmmodule_mf"/>

8. creditCard.xml  - removed email field

9. In /sfra/cartridge/templates/default/checkout/billing/billing.isml - added email field HTML

10. In /sfra/cartridge/templates/default/checkout/billing/billingSummary.isml - added div 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>

11. In /sfra/cartridge/templates/default/checkout/billing/creditCardForm.isml - removed email field from credit card HTML

12. /sfra/cartridge/templates/default/checkout/billing/paymentOption.isml - added condition for switching payment methods if Affirm is not applicable.

<div class="form-nav billing-nav payment-information"
    data-payment-method-id="<isif condition="${require('int_affirm/cartridge/scripts/utils/affirmHelper').IsAffirmApplicable()}">Affirm<iselse>CREDIT_CARD</isif>"
    data-is-new-payment="${pdict.customer.registeredUser && pdict.customer.customerPaymentInstruments.length ? false : true}"
>

13. /sfra/cartridge/templates/default/checkout/confirmation/confirmation.isml before closing </isdecoratre> tag

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

Controllers:

NOTE: CheckoutServices.js was overridden for handling both Affirm and Credit card payments.

14. js/checkout/billing.js - added holder for updating payment information.

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

15. Add checks if credit card fields are absent.

if ($('input[name$=cardNumber]').length){
    $('input[name$=cardNumber]').data('cleave').setRawValue('');
}

16. In js/checkout/checkout.js - add checking to ensure  affirm object is formed properly.

if (checkoutStages[currentStage] == 'payment'){
    if ($('#affirm-config').data('affirmenabled')){                             
        $('.affirm-payment-tab').trigger('click');
    }
}

17. Added insertion of Modal mode to checkout steps as a reminder to close the curly bracket.

(stage === 'placeOrder' && ($('#affirm-config').data('mode') !== 'modal' || $('#affirm-config').data('vcnenabled') === true)) {
                  
             if( $('#vcn-data').data('vcncomplete') || !$('#affirm-config').data('vcnenabled') ) {

18. In js/cart/cart.js - Added recalculation for the Affirm Modal Promo price.

$('.affirm-as-low-as').attr('data-amount', (totalCalculated * 100).toFixed());
   affirm.ui.refresh();

19. Rebuild the site

2. Custom Code for Controllers and Pipelines

1. Find and open the below template: storefront/cartridge/templates/default/components/header/htmlhead.isml. Paste the following code after the  “Google verification feature” as it shown on the screenshot below:

<isinclude template="affirm/affirmheader" />

2. Find and open template – storefront/cartridge/templates/default/components/footer/footer_UI.isml. Paste code after the last line:

<isinclude template="affirm/affirmfooter" />

3. Find and open template – storefront/default/checkout/cart/cart.isml.   Find the closing tag of div with class “cart-footer” (line 850). Paste code after the below: 

<isaffirmpromo context="cart" fpname="${require('int_affirm/cartridge/scripts/utils/affirmUtils').getFPNameByBasket(pdict.Basket)}">

 

4. Find and open template: storefront/cartridge/templates/default/product/productcontent.isml.  Paste code after “pricing component”:

<isinclude template="util/affirmmodule"/>
<isaffirmpromo context="pdp" fpname="${require('int_affirm/cartridge/scripts/utils/affirmUtils').getFinancingProgramByProduct(pdict.Product, true)}" />

5. Find and open template – storefront/default/checkout/billing/paymentmethods.isml. Find the following code:

Replace it with the  code below:

<isif condition="${paymentMethodType.value.equals('Affirm')}">
    <isinclude template="affirm/paymentmethodinput" />
<iselse/>
    <div class="form-row label-inline">
        <isset name="radioID" value="${paymentMethodType.value}" scope="page"/>
        <div class="field-wrapper">
            <input id="is-${radioID}" type="radio" class="input-radio" name="${pdict.CurrentForms.billing.paymentMethods.selectedPaymentMethodID.htmlName}" value="${paymentMethodType.htmlValue}" <isif condition="${paymentMethodType.value == pdict.CurrentForms.billing.paymentMethods.selectedPaymentMethodID.htmlValue}">checked="checked"</isif> />
        </div>
        <label for="is-${radioID}"><isprint value="${Resource.msg(paymentMethodType.label,'forms',null)}"/></label>
    </div>
</isif>

Find the following code:

Paste code after:

<isinclude template="affirm/affirmpaymentmethod" />

 

6. Find and open template: storefront/cartridge/templates/default/checkout/summary/summary.isml. Go to the end of the “COSummary-Submit” form and paste the following code after it

<isinclude template="affirm/vcndata" />

7. Find and open template: int_affirm_controllers/templates/default/util/affirmpromo.isml.  Remove the following code on line 8

${pdict.fpname}

clipboard_e9c185986cef359aebb0115fdcf5a24bc.png

8. Find and open template: storefront/cartridge/templates/default/product/producttile.isml.  Paste the code below after the “product swatches block”

<isinclude template="util/affirmmodule"/>
<isaffirmpromo context="plp" fpname="${require('int_affirm/cartridge/scripts/utils/affirmUtils').getFPNameForPLP(pdict.CurrentHttpParameterMap.cgid.value, Product)}" price="${prices}"/>

9. Find and open template: storefront/cartridge/templates/default/product/producttopcontentPS.isml. Find the following code (lines 118-120)

Paste code after:

<isinclude template="util/affirmmodule"/>
<isaffirmpromo context="pdp" fpname="${require('int_affirm/cartridge/scripts/utils/affirmUtils').getFinancingProgramByProduct(pdict.Product, true)}" />

10. Find and open template: storefront/cartridge/templates/default/util/modules.isml.   Paste code after the last line

<isinclude template="util/affirmmodule"/>

 

11. Find and open template: storefront/cartridge/templates/default/components/order/orderdetails.isml. Find the following code (lines 63-64)

Replace it with the code below:

<isif condition="${!paymentInstr.custom.affirmed}">
    <div class="payment-type">
        <isprint value="${dw.order.PaymentMgr.getPaymentMethod(paymentInstr.paymentMethod).name}" />
    </div><isminicreditcard card="${paymentInstr}" show_expiration="${false}"/>
<iselse/>
    <isaffirmpaymenttype email="${false}"/>
</isif>

 12. Find and open template: storefront/cartridge/templates/default/components/order/orderdetailsemail.isml. Find the following code (lines 46-50)

Replace it with the code below:

<isif condition="${!paymentInstr.custom.affirmed}">
    <div>
        <isprint value="${dw.order.PaymentMgr.getPaymentMethod(paymentInstr.paymentMethod).name}" />
    </div>
    <isif condition="${dw.order.PaymentInstrument.METHOD_GIFT_CERTIFICATE.equals(paymentInstr.paymentMethod)}">
        <isprint value="${paymentInstr.maskedGiftCertificateCode}"/>
        <br />
    </isif>
    <isminicreditcard card="${paymentInstr}" show_expiration="${false}"/>
<iselse/>
    <isaffirmpaymenttype email="${true}"/>
</isif>

13. Find and open template: storefront/cartridge/templates/default/checkout/confirmation/confirmation.isml. Paste the following code after the div “confirmation message” closing tag.

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

14. Find and open client JS file – storefront/cartridge/js/pages/product/variant.js.  Find the following code:

Paste code after:

if (typeof affirm !== "undefined"){
    affirm.ui.refresh();
}

 

15. Find and open client JS file – storefront/cartridge/js/pages/search.js.  Find the following code:

Paste code after:

if (typeof affirm !== "undefined"){

    affirm.ui.refresh();
}

  

16. Find and open property file – int_affirm_controllers/cartridge/templates/resources/affirm.properties.  Find the property “affirm.controllers.cartridge”. Set it as the name of your controllers cartridge (with script files app.js, guard.js), for example:

Troubleshooting:

If you are not seeing promotional messaging in the PDP page after following the above steps please make the following change:

Find and open template: int_affirm_controllers/templates/default/util/affirmpromo.isml.

Change line 21 to read: <isset name="psTop" value="${typeof product == 'undefined' && !empty(pdict.Product)}" scope="page"/>

2.1 If you are using Controllers do the following:

1. Find and open controller: storefront/cartridge/controllers/COBilling.js. Find the following code:

Paste code after: 

applicablePaymentMethods = require('*/cartridge/controllers/Affirm').Init(cart, applicablePaymentMethods);

Find the following code (lines 174-179):

Replace the last line with the following code:

var applicablePaymentMethods = require('*/cartridge/controllers/Affirm').Init(cart, creditCardList.ApplicablePaymentMethods);

Find the following code (lines 351-372) 

 

Replace the code with the following code:

var status = Transaction.wrap(function () { 
    if (app.getForm('billing').object.paymentMethods.selectedPaymentMethodID.value.equals('PayPal')) { 
        
        app.getForm('billing').object.paymentMethods.creditCard.clearFormElement();
        app.getForm('billing').object.paymentMethods.bml.clearFormElement();
        cart.removePaymentInstruments(cart.getPaymentInstruments(PaymentInstrument.METHOD_CREDIT_CARD));
        cart.removePaymentInstruments(cart.getPaymentInstruments(PaymentInstrument.METHOD_BML));
        cart.removePaymentInstruments(cart.getPaymentInstruments('Affirm'));
         
    } else if (app.getForm('billing').object.paymentMethods.selectedPaymentMethodID.value.equals(PaymentInstrument.METHOD_CREDIT_CARD)) {
        
        app.getForm('billing').object.paymentMethods.bml.clearFormElement();
        cart.removePaymentInstruments(cart.getPaymentInstruments(PaymentInstrument.METHOD_BML));
        cart.removePaymentInstruments(cart.getPaymentInstruments('PayPal'));
        cart.removePaymentInstruments(cart.getPaymentInstruments('Affirm'));
        
    } else if (app.getForm('billing').object.paymentMethods.selectedPaymentMethodID.value.equals(PaymentInstrument.METHOD_BML)) {
        
        app.getForm('billing').object.paymentMethods.creditCard.clearFormElement();
        
        if (!app.getForm('billing').object.paymentMethods.bml.ssn.valid) {
            return false;
        }
        
        cart.removePaymentInstruments(cart.getPaymentInstruments(PaymentInstrument.METHOD_CREDIT_CARD));
        cart.removePaymentInstruments(cart.getPaymentInstruments('PayPal'));
        cart.removePaymentInstruments(cart.getPaymentInstruments('Affirm'));
    
    } else if (app.getForm('billing').object.paymentMethods.selectedPaymentMethodID.value.equals('Affirm')) {
        cart.removePaymentInstruments(cart.getPaymentInstruments(PaymentInstrument.METHOD_CREDIT_CARD));
        cart.removePaymentInstruments(cart.getPaymentInstruments(PaymentInstrument.METHOD_BML));
        cart.removePaymentInstruments(cart.getPaymentInstruments('PayPal'));
    }
    
    return true;
    
});

return status;

 

2. Find and open controller: storefront/cartridge/controllers/COPlaceOrder.js.  

a. Find the following code (lines 134-141):

Paste code after:

var affirmController = require('int_affirm_controllers/cartridge/controllers/Affirm');
var affirmCheck = affirmController.CheckCart(cart);

if (affirmCheck.status.error){ 
    return { 
        error: true, 
        PlaceOrderError: affirmCheck.status
    };
}

b. Find the following code (lines 164-188)

clipboard_ea1dd4306b7965c2fb1c6c85c32e46df3.png

Replace with the following code:

var handlePaymentsResult = handlePayments(order);


    if (handlePaymentsResult.error) {
        return Transaction.wrap(function () {
        var affirmOrder = require('int_affirm/cartridge/scripts/affirm').order;
        affirmOrder.orderCanceledVoid(order);
            OrderMgr.failOrder(order);
            
            return {
                error: true,
                PlaceOrderError: new Status(Status.ERROR, 'confirm.error.technical')
            };
        });


    } else if (handlePaymentsResult.missingPaymentInfo) {
        return Transaction.wrap(function () {
        var affirmOrder = require('int_affirm/cartridge/scripts/affirm').order;
        affirmOrder.orderCanceledVoid(order);
        
            OrderMgr.failOrder(order);
            return {
                error: true,
                PlaceOrderError: new Status(Status.ERROR, 'confirm.error.technical')
            };
        });
    }


    var orderPlacementStatus = Order.submit(order);
    if (!orderPlacementStatus.error) {
        clearForms();
        affirmController.PostProcess(order);
    }  
    return orderPlacementStatus;

3. Find and open controller – storefront/cartridge/controllers/COSummary.js.  Find the “submit” function. Add the below code at the beginning of the function:

var redirected = require('int_affirm_controllers/cartridge/controllers/Affirm').Redirect();
if (redirected) {
    return;
}    

4. Rebuild the site

 

2.2 If you are using pipelines do the following:

1. Add Affirm.xml to /int_affirm/cartridge/pipelines/Affirm.xml

2. Find and open pipeline COBilling. Find start node “InitCreditCardList” and add call node (Affirm-Init) after second Assign node:

3. Find start node “Reset PaymentForms” and add Pipelet nodes like in screenshot:

4. In the same pipeline create addition branch:

a. Decision node – Decision Key: !CurrentForms.billing.paymentMethods.selectedPaymentMethodID.value.equals('Affirm')'

b. ClearFormElement Pipelet – Form Element: CurrentForms.billing.paymentMethods.creditCard

c. ClearFormElement Pipelet – Form Element: CurrentForms.billing.paymentMethods.bml

d. RemoveBasketPaymentInstrument Pipelet – PaymentInstruments: Basket.getPaymentInstruments(dw.order.PaymentInstrument.METHOD_CREDIT_CARD)

e. RemoveBasketPaymentInstrument Pipelet – PaymentInstruments: Basket.getPaymentInstruments( dw.order.PaymentInstrument.METHOD_BML)

5. Find and open pipeline COPlaceOrder. Find start node “Start” and add script node after call node “COBilling-ValidatePayment” (see screenshot below). Add call script Affirm-CheckBasket

5.a. Add Error redirect

addCartShow.png

5.b. Add Error transition

AddErrorTransition.png

6. Find and open pipeline COPlaceOrder. Find the “FailOrder” node and add the following pipelet above it:

VoidAffirmOrder.png

VoidErrorPipelet.png

7. Modify /int_affirm_controllers/cartridge/controllers/Affirm.js to override the existing methods to follow pipelines instead. There may also be existing modifications that need to be made that depend on your existing set environment.

/**
* Handle successful response from Affirm
*/
function success() {
     let Pipeline = require('dw/system/Pipeline');
     let placeOrderResult = Pipeline.execute('COPlaceOrder-Start',{a:'a'});
 
     if (placeOrderResult.EndNodeName == 'error') {
          let COS_start = Pipeline.execute('COSummary-Start',{
              PlaceOrderError : placeOrderResult.PlaceOrderError
          });
     }
     else if (placeOrderResult.EndNodeName == 'order_created')
      {
 
         if (!customer.authenticated) {
             // Initializes the account creation form for guest checkouts by populating the first and last name with the
             // used billing address.
             
             var customerForm = CurrentForms.profile.customer;
                 customerForm.firstname.value = placeOrderResult.Order.billingAddress.firstName;
                 customerForm.lastname.value = placeOrderResult.Order.billingAddress.lastName;
                 customerForm.email.value = placeOrderResult.Order.customerEmail;
         }
         CurrentForms.profile.login.passwordconfirm.value = '';
         CurrentForms.profile.login.password.value = '';
        
         ISML.renderTemplate('checkout/confirmation/confirmation', {
            Order: placeOrderResult.Order
//            ContinueURL: URLUtils.https('Account-RegistrationForm'),
        });
    }
}

function updateBasket(){
   if (!dw.web.CSRFProtection.validateRequest()){
     response.writer.print(JSON.stringify({error: true}));
        return;
}
   
   var hookName = "dw.int_affirm.payment_instrument." + affirm.data.VCNPaymentInstrument();
   var basket = BasketMgr.getCurrentBasket();
   
response.setContentType('application/json');
Transaction.wrap(function(){
      var iter = basket.getPaymentInstruments(AFFIRM_PAYMENT_METHOD).iterator();
      // Remove payment instruments.
      while (iter.hasNext()) {
      basket.removePaymentInstrument(iter.next());
   }
});
   if (dw.system.HookMgr.hasHook(hookName)){
   
        var paymentInstrument = dw.system.HookMgr.callHook(hookName, "add", basket);
        
        if (!paymentInstrument){
       
        response.writer.print(JSON.stringify({error: true}));
            return;
        } else {
       
         Transaction.wrap(function(){
              paymentInstrument.custom.affirmed = true;
      });
     }
} else {
     response.writer.print(JSON.stringify({error: true}));
       return;
}
  
response.writer.print(JSON.stringify({error: false}));
}

7. Rebuild the site

  • Was this article helpful?