Extend the Salesforce B2C LINK Cartridge
Extend the Salesforce B2C LINK Cartridge.
You can extend the functionality and features of the Salesforce B2C LINK Cartridge to meet your integration needs. Read the following section to learn how you can do this.
Custom code
SFRA templates
This section describes changes applied to the most recent templates provided with the cartridge. In addition, the section describes changes that may need to be made to templates to support specific client use cases and integration choices.
The following templates and their changes are described in this section:
Add payment
Use the add payment (addPayment.isml
) template to add payment cards to the customer's account. You can use the template to add Drop-in payments functionality (DigitalRiver.js) and Drop-in payments in styles (DigitalRiver.css), as well as place Drop-in payments on an account page.
Template: cartridge/templates/default/account/payment/addPayment.isml

Added this Digital River script to provide Drop-in payments functionality on the page and apply related changes to Drop-in payments' styles.
<script src="https://js.digitalriverws.com/v1/DigitalRiver.js"></script>
<link rel="stylesheet" href="https://js.digitalriverws.com/v1/css/DigitalRiver.css" type="text/css"/>
Applied the changes to Drop-in payments' styles.
assets.addCss('/css/digitalRiver.css');

Added the following condition inside the card-body
div
.
<iscomment> Include Digital River Drop-in </iscomment>
<isset name="useDigitalRiverDropIn" value="${require('dw/system/Site').getCurrent().getCustomPreferenceValue('drUseDropInFeature')}" scope="page" />
<isif condition="${useDigitalRiverDropIn}">
<isinclude template="account/payment/dropinForm"/>
<isinclude url="${URLUtils.url('DigitalRiver-DisplayCompliance', 'complianceId', 'compliancePayment')}" />
<iselse/>
<iscomment> Default Payment form </iscomment>
<isinclude template="account/payment/paymentForm"/>
<iscomment> Default Payment form </iscomment>
<isinclude template="account/payment/paymentForm"/>
</isif>

Added the following code:
<isif condition="${pdict.drCustomerError}">
<div class="alert alert-danger" role="alert">
<p class="error-message-text">${pdict.drCustomerError}</p>
</div>
<iselse/>
…
</isif>

Dashboard profile cards
Use the dashboard profile cards (dashboardProfileCards.isml
) template to add a Digital River Tax Certificates section to your Profile Dashboard page. The following image shows the out-of-the-box (OOTB) Dashboard Profile Dashboard page for Salesforce B2C Link Cartridge.

When you apply the dashboard profile cards template, it adds Tax Certificates.

Template path: cartridge/templates/default/account/dashboardProfileCards.isml
Added the Digital River tax certificate section on the page. In this example, tax certificates are displayed in My account only for the US locale. This section is available for US locale only.
<!---Digital River Tax Certificates--->
<isif condition "${pdict.localeCountry === 'US'}">
<isinclude template="digitalriver/account/drTaxCertificateCard"/>
</isif>

Cart totals
Use the cart totals (cartTotals.isml
) template to include the Digital River Order Summary section on your Checkout page.

Template: cartridge/templates/default/cart/cartTotals.isml
Included the Digital River Taxations section in the cart totals template after the discount section.
<!-- Digital River Taxations -->
<isif condition="${require('dw/system/Site').getCurrent().getCustomPreferenceValue('drUseDropInFeature')}">
<isif condition="${pdict.totals.isImporterOfRecordTax || pdict.totals.duty.value !== 0}">
<div class="row">
<div class="col-8">
<p>${Resource.msg('label.order.sales.duty','digitalriver', null)}</p>
</div>
<div class="col-4">
<p class="text-right duty-total">${pdict.totals.duty.formatted}</p>
</div>
</div>
</isif>
<isif condition="${pdict.totals.isImporterOfRecordTax || pdict.totals.importerTax.value !== 0}">
<div class="row">
<div class="col-8">
<p>${Resource.msg('label.order.sales.importerTax','digitalriver', null)}</p>
</div>
<div class="col-4">
<p class="text-right importerTax-total">${pdict.totals.importerTax.formatted}</p>
</div>
</div>
</isif>
<isif condition="${pdict.totals.isImporterOfRecordTax || pdict.totals.totalFees.value !== 0}">
<div class="row">
<div class="col-8">
<p>${Resource.msg('label.order.sales.totalFees','digitalriver', null)}</p>
</div>
<div class="col-4">
<p class="text-right totalFees-total">${pdict.totals.totalFees.formatted}</p>
</div>
</div>
</isif>
</isif>

Payment options summary
As part of Billing, you can use the payment options summary template (paymentOptionsSummary.isml
) to extend the payment condition with the DIGITAL_RIVER_DROPIN
payment method on the Checkout page.

Template path: cartridge\templates\default\checkout\billing\paymentOptions\paymentOptionsSummary.isml
This change extended the payment condition with the ‘DIGITAL_RIVER_DROPIN’
payment method.
<iselseif condition="${payment.paymentMethod === 'DIGITAL_RIVER_DROPIN'}" />
<isinclude template="checkout/billing/paymentOptions/dropInSummary" />

Billing
Use the billing (billing.isml
) template to add accordion components to the Billing page. The following image shows the out-of-the-box (OOTB) Payment section of the Checkout page for Salesforce B2C Link Cartridge.

When you apply the dashboard profile cards template, it adds the ability to choose whether the purchase is tax-exempt and the payment method.

Template path: cartridge/templates/default/checkout/billing/billing.isml
Added the accordion components to the billing page. In this example, accordion billing is used by extending the tag with id="accordionBilling"
.
<div class="accordion" id="accordionBilling" data-digital-cart="${pdict.isDigitalCart}">

Introduced a new templated purchase site within the parent template.
<isinclude template=
"digitalriver/checkout/billing/drPurchaseTypeSection"/>
Extended the Digital River tax identifier section <div>
tag with the dr-accordion-card
class and the remove wrap expression in highlighted code.
<div id="tax-id-accordion" class="dr-accordion-card ${pdict.digitalRiverUseTaxIdentifier ? '' : 'hidden'}">

Added accordion components to the billing page.
<iscomment> Digital River accordion section </iscomment>
<isif condition="${pdict.digitalRiverUseDropInFeature}">
</div></div></div></div>
</isif>
ude Add Tax certificate modal window component
<isinclude template="digitalriver/checkout/billing/drTaxCertificateModal" />

Modified tag with the following line:
<div class="dr-accordion-card-header ${pdict.order.totals.isZeroTotal ? 'digitalriver-hide' : ''}" id="headingPayment">

Stored payment instruments
Use the stored payment instruments (storedPaymentInstruments.isml
) template to display the stored payment instruments on the Billing page as a list of saved payment cards in the customer's account.
Template path: cartridge/templates/default/checkout/billing/storedPaymentInstruments.isml

Added the following code at the beginning of the file:
<isset name="isDRDropInEnabled" value="${require('dw/system/Site').getCurrent().getCustomPreferenceValue('drUseDropInFeature')}" scope="page" />
<div class="invalid-feedback" id="savedPaymentNotSelectedMessage">${Resource.msg('error.select.stored.card', 'digitalriver', null)}</div>
Extended the card-image
class condition as follows:
<img class="card-image" src="${paymentInstrument.cardTypeImage.src}" alt="${paymentInstrument.cardTypeImage.alt}">
Wrapped the security-code-input div with the following condition:
<isif condition="${!isDRDropInEnabled}"> <iscomment> Digital River - if enabled no cvv needed thus pictures are always shown instead of input </iscomment>
<div class="security-code-input ${loopState.first ? '' : 'checkout-hidden'}">
<label class="form-control-label" for="saved-payment-security-code">${Resource.msg('label.credit.card-security.code','checkout',null)}</label>
… security-code-unput goes here
</div>
</isif>

Confirmation
Use the standard confirmation (confirmation.isml
) template for the B2C LINK Cartridge to include the Digital River compliance statement.
The customer should agree to the Terms of Sale and the Privacy Policy of Digital River and set the checkbox to complete an order. For more information on the Terms of Sale and the Privacy Policy of Digital River, visit the Digital River Legal Documentation website.
Compliance links are required to be displayed in several different places throughout the storefront. The out-of-the-box (OOTB) cartridge displays the compliance links in all required locations. Consult your Digital River Project Manager before making modifications to your storefront. For more detailed information on the purpose of the compliance links, visit Compliance on the Digital River Legal Documentation website.
Template path: cartridge/templates/default/checkout/confirmation/confirmation.isml

Added the DigitalRiver.js script to the page.
<script type="text/javascript" src="https://js.digitalriverws.com/v1/DigitalRiver.js"></script>
<link rel="stylesheet" href="https://js.digitalriverws.com/v1/css/DigitalRiver.css" type="text/css"/>
Added the Digital River compliance section to the page.
<div class="row">
<div class="${pdict.returningCustomer ? 'col-sm-6 offset-sm-3' : 'col-sm-6 offset-sm-3 offset-md-0 pull-md-6' }">
<isinclude template="digitalriver/compliance" />
</div>
</div>


Added a Digital River styles file.
assets.addCss('/css/digitalRiver.css');

Added an include
of delayedPaymentInstructions
(first highlighted section).
<isinclude template="digitalriver/delayedPaymentInstructions" />
Added an include
of delayedPaymentInstructions
(second highlighted section).
<isif condition="${pdict.returningCustomer}">
<isinclude template="digitalriver/delayedPaymentInstructions" />
</isif>

Checkout
Use the checkout (checkout.isml
) template to add Digital River styles, the Drop-in styles, and the check box to confirm agreement with the Terms of Sale and the Privacy Policy of Digital River to the Checkout page when the customer is ready to place an order. The customer should agree to the Terms of Sale and the Privacy Policy of Digital River and set the check box to complete the order.
Template: cartridge/templates/default/checkout/checkout.isml

Added the Digital River Drop-in styles.
<link rel="stylesheet" href="https://js.digitalriverws.com/v1/css/DigitalRiver.css" type="text/css"/>
Included the Digital River styles.
assets.addCss('/css/digitalRiver.css');
assets.addCss('/css/drAccordion.css');

Added a spinner inside the placeOrder
stage condition. When Shopper is in placeOrder
stage, they will see a spinner running until the place order stage executes.
$('#checkout-main').spinner().start();

Added the Digital River confirm disclosure checkbox to the checkout page.
<isinclude template="digitalriver/confirmDisclosure" />
Modified the button to submit shipping.
<button class="btn btn-primary btn-block submit-shipping" type="submit" name="submit" value="submit-shipping" <isif condition="${(pdict.order.usingMultiShipping && !pdict.order.shippable) || pdict.drCustomerError}">disabled</isif>>
Added a condition that will control the submit payment button display.
<isset name="submitPaymentShow" value="${!pdict.digitalRiverUseDropInFeature || (pdict.customer.registeredUser && pdict.customer.customerPaymentInstruments.length) || pdict.order.totals.isZeroTotal ? '' : 'digitalriver-hide'}" scope="page" />
<button class="btn btn-primary btn-block submit-payment ${submitPaymentShow}" type="submit" name="submit" value="submit-payment" data-saved-payments="${pdict.customer.registeredUser && pdict.customer.customerPaymentInstruments.length}">
Added a reference to the drPlaceOrder isml template.
<isinclude template="digitalriver/checkout/placeOrder/drPlaceOrder" />
Added the Digital River compliance section to the checkout page.
<div class="row">
<div class="col-12 next-step-button">
<div class="mb-sm-3" id="ch">
<isinclude url="${URLUtils.url('DigitalRiver-DisplayCompliance', 'complianceId', 'checkoutCompliance')}" />
</div>
</div>
</div>

Added a display for an error message.
<p class="error-message-text">
<isif condition="${pdict.errorMessage}">${pdict.errorMessage}</isif>
</p>

Added the following code to display a customer related error:
<isif condition="${pdict.drCustomerError}">
<div class="alert alert-danger dr-customer-error" role="alert">
<p class="error-message-text">${pdict.drCustomerError}</p>
</div>
</isif>

Order total summary
Use the order total summary (orderTotalSummary.isml
) template to add the Digital River Taxations section to your Checkout page.
Template path: cartridge/templates/default/checkout/orderTotalSummary.isml

Added the "Sales Tax" information text as conditional HTML content.
<isif condition="${dw.order.TaxMgr.getTaxationPolicy() !== dw.order.TaxMgr.TAX_POLICY_GROSS}">
<div class="row leading-lines sales-tax-item">
… 'Sales Tax' html content goes here
</div>
</isif>

Added the Digital River Taxations section.
<!--- Digital River Taxations --->
<isif condition="${require('dw/system/Site').getCurrent().getCustomPreferenceValue('drUseDropInFeature')}">
<div class="row leading-lines duty-item ${!pdict.order.totals.isImporterOfRecordTax && pdict.order.totals.duty.value === 0 ? 'hide-order-discount': ''}">
<div class="col-6 start-lines">
<p class="order-receipt-label"><span>${Resource.msg('label.order.sales.duty','digitalriver', null)}</span></p>
</div>
<div class="col-6 end-lines">
<p class="text-right"><span class="duty-total">${pdict.order.totals.duty.formatted}</span></p>
</div>
</div>
<div class="row leading-lines importerTax-item ${!pdict.order.totals.isImporterOfRecordTax && pdict.order.totals.importerTax.value === 0 ? 'hide-order-discount': ''}">
<div class="col-6 start-lines">
<p class="order-receipt-label"><span>${Resource.msg('label.order.sales.importerTax','digitalriver', null)}</span></p>
</div>
<div class="col-6 end-lines">
<p class="text-right"><span class="importerTax-total">${pdict.order.totals.importerTax.formatted}</span></p>
</div>
</div>
<div class="row leading-lines totalFees-item ${!pdict.order.totals.isImporterOfRecordTax && pdict.order.totals.totalFees.value === 0 ? 'hide-order-discount': ''}">
<div class="col-6 start-lines">
<p class="order-receipt-label"><span>${Resource.msg('label.order.sales.totalFees','digitalriver', null)}</span></p>
</div>
<div class="col-6 end-lines">
<p class="text-right"><span class="totalFees-total">${pdict.order.totals.totalFees.formatted}</span></p>
</div>
</div>
</isif>

Added the Digital River gross site VAT info to the end of the file.
<!--- Digital River Gross Site VAT Info --->
<isif condition="${require('dw/system/Site').getCurrent().getCustomPreferenceValue('drUseDropInFeature') && dw.order.TaxMgr.getTaxationPolicy() === dw.order.TaxMgr.TAX_POLICY_GROSS}">
<isif condition="${!pdict.order.orderNumber}">
<p class="vat-msg vat-included-msg ${pdict.order.totals.drTaxDiscountTotal.value === 0 ? '': 'hidden'}">
${Resource.msg('msg.vat.included', 'digitalriver', null)}
</p>
<p class="vat-msg vat-exempted-msg ${pdict.order.totals.drTaxDiscountTotal.value === 0 ? 'hidden': ''}">
<span class="vat-exempted-value">${pdict.order.totals.drTaxDiscountTotal.formatted}</span> ${Resource.msg('msg.vat.exempted', 'digitalriver', null)}
</p>
</isif>
<isif condition="${pdict.vatReflectMsg}">
<p class="vat-msg vat-reflect-msg">${pdict.vatReflectMsg}</p>
</isif>
</isif>

Payment
Use the Payment (payment.isml
) template to add a payment section to your Checkout page.
Template path: cartridge/templates/default/checkout/payment.isml
Added the following code on lines 14-18, line 41:
<isif condition="${pdict.drCustomerError}">
<div class="alert alert-danger" role="alert">
<p class="error-message-text">${pdict.drCustomerError}</p>
</div>
<iselse/>
…
</isif>

Order Details
Use the OrderDetails (orderDetails.isml
) template to display order details to the shopper after the transaction has been completed.
Template path: cartridges/int_digitalriver_sfra/cartridge/templates/default/account/orderDetails.isml
In the <isscript>
section, added a reference to the drInvoiceCredit.js
script.
assets.addJs('/js/drInvoiceCredit.js');
Added remote include for Invoices and Credit memos and Offline Refund functionality.
<iscomment> Include DR invoice & credit memo links </iscomment>
<isinclude url="${URLUtils.url('DigitalRiver-InvoiceCredit', 'id', pdict.order.drOrderID)}" />
<isinclude url="${URLUtils.url('DigitalRiver-OfflineRefund', 'id', pdict.order.drOrderID, 'sfOrderID', pdict.order.orderNumber)}" />

Menu
Use the Menu (menu.isml
) template to display the country/currency selector for the dynamic pricing feature.
Note: This template is not overridden by the cartridge OOTB. Use the following example to include the country/currency selector on the storefront. You are expected to add the selector to the desired location.
Template path: cartridge/templates/default/components/header/menu.isml
At the end of the <ul class="nav navbar-nav" role="menu">
section, add a remote include for the country and currency selection menu.
<div class="d-sm-block d-md-none">
<isinclude url="${URLUtils.url('DigitalRiver-CountryCurrencySelector', 'mobile', true)}" />
</div>

Page Header
Use the Page Header (pageHeader.isml
) template to display the country/currency selector for the dynamic pricing feature.
Template path: cartridge/templates/default/components/header/pageHeader.isml
At the end of the <div class="hidden-md-down"
> section, add a remote include for the country and currency selection menu.
<isinclude url="${URLUtils.url('DigitalRiver-CountryCurrencySelector')}" />

Cart
Override the (cart.isml
) template to conditionally display the shipping method selector on the cart page.
Template path:cartridge/templates/default/cart/cart.isml
Locate the <isinclude template="cart/cartShippingMethodSelection"/>
and replace it with the following:
<isif condition="${pdict.showShippingList}">
<isinclude template="cart/cartShippingMethodSelection" />
</isif>

SFRA client scripts
This section describes the changes applied to the most recent client scripts provided with the cartridge. In addition, the section describes changes that you may need to make to the client scripts to support your specific use cases and integration choices.
The following scripts and their changes are described in this section:
Checkout script
The checkout (checkout.js
) script is loaded on the Checkout page and includes the page frontend event handler. The script loads the base event handler (checkout.js
) and additionally loads the Digital River US Tax Certificate handler (drCertificate.js
) on the shipping stage of the checkout and the Global Tax ID handler (drTaxId.js
) on the billing stage of checkout.
Script path: cartridge/client/default/js/checkout.js
Included the additional scripts on the checkout page.
processInclude(require('./checkout/drDropIn'));
processInclude(require('./checkout/drCertificate'));
processInclude(require('./checkout/drTaxId'));

Script path: cartridge/client/default/js/checkout/checkout.js
The checkout script (checkout.js
) is loaded on the Checkout page and includes a basic page frontend event handler. The changes introduced by this script extend the base functionality to correctly handle Digital River extensions.
Added the following line at the beginning of the file:
var drHelper = require('./drHelper');

Added the retrieve stored card global variable at the beginning of the checkout function.
// --- Digital River Retrieve Stored Card ---
var drStoredPayment = null;

Added the following code to the shipping submit success handler.
if (!data.error && data.digitalRiverConfiguration) {
drHelper.updateComplianceEntity(data.digitalRiverComplianceOptions.compliance.businessEntityCode);
$('body').trigger('digitalRiver:dropIn', data.digitalRiverConfiguration); // Digital River integration: call dropIn feature after checkoutCreate
$('body').trigger('digitalRiver:taxIdentifier', data.digitalRiverTaxIdConfig);
$('body').trigger('digitalRiver:taxCertificate', data.digitalRiverTaxExemptData);
}

Added the following code to the Retrieve stored card section.
drStoredPayment = null; // --- Digital River Retrieve Stored Card --de
Extended the code as follows.
var paymentMethod = $('.payment-information').data('payment-method-id');
if (paymentMethod === 'CREDIT_CARD' || paymentMethod === 'DIGITAL_RIVER_DROPIN') { // Extended by Digital River Drop-in integration
if ($('.saved-payment-instrument.selected-payment').length === 0) {
$('#savedPaymentNotSelectedMessage').show();
$('#collapse-payment').collapse('show');
defer.reject();
return defer;
}
Added the following code to the Retrieve stored card section.
// --- Digital River Retrieve Stored Card ---
drStoredPayment = $savedPaymentInstrument.data('uuid')

Extended the billing code as follows:
$('#collapse-billing').collapse('show');

Extended the code as follows:
drHelper.renderDRConfirm();

Wrapped the contents of the placeOrder
stage function.
// --- Digital River Retrieve Stored Card ---
var placeOrder = function (defer) { // eslint-disable-line no-shadow
//Digital River - 2.6 - Redirect Flow
if (!$('.DR-place-order').data('dr-order-placed') && !$('.DR-place-order').data('dr-redirect-success'))
{
drHelper.handleDROrderPlacement(defer, placeOrder);
}
//End Digital River - 2.6 - Redirect Flow
else {
$('.DR-place-order').data('dr-order-placed',"false");
… 'placeOrder' stage content goes here
if (data.digitalRiverConfiguration) {
$('body').trigger('digitalRiver:updateDropIn', data.digitalRiverConfiguration);
}
… 'placeOrder' stage content goes here
drHelper.retrieveStoredCard(drStoredPayment, defer, placeOrder);

Added the following code to the shipping submit success handler.
$('body').trigger('digitalRiver:taxCertificate', data.digitalRiverTaxExemptData);

Added to ensure that the proper redirection takes place after returning from payment.
//Digital River - 2.6 - Redirect Flow
$(document).ready(function () {
drHelper.handleDROrderRedirect(members);
});

Billing script
Use the billing.js
script to extend the checkout script functionality.
Script path: cartridge/client/default/js/checkout/billing.js
Modified the following and also modified the row (see highlighted area).
$('[name$=' + element + ']', form).val(attrs[attr]).trigger('change');

The default storefront on the Checkout page uses the payment instruments (paymentInstruments.js
) script. This JavaScript includes the frontend handler for payment cards. This frontend JavaScript must be completed and compiled before being used on the site. When this JavaScript is loaded on the Checkout page, the script loads the base handler (paymentInstruments.js
) and additionally loads the handler for the Digital River payment cards (paymentInstrumentsDropIn.js
).
Used proccessInclude
to extend the default paymentInstruments.js script. Once compiled, the paymentInstrumentsDropIn.js
extends the Checkout page to display the available payment methods. The script contains functions to launch Drop-in, handle errors, and handle submitted payments.
Script path: cartridge/client/default/js/checkout/billing.js
if ($('#dropInContainer').data('enabled')) return;

Edit the updatePaymentInformation
as follows:
if (order.billing.payment && order.billing.payment.selectedPaymentInstruments
&& order.billing.payment.selectedPaymentInstruments.length > 0) {
if (order.billing.payment.selectedPaymentInstruments[0].paymentType === 'creditCard') {
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 if (order.billing.payment.selectedPaymentInstruments[0].paymentMethod !== 'DIGITAL_RIVER_Z`

Modify the selectBillingAddress
function. An example is shown in line 267 of the following screenshot:
$('[name$=' + element + ']', form).val(attrs[attr]).trigger('change');

Add the clearCreditCardForm
function content and include the following condition:
if ($('#dropInContainer').data('enabled')) return;

Add the handleCreditCardNumber
function and include the following condition:
if ($('.cardNumber').length === 0) return;

Add the following condition inside the selectSavedPaymentInstrument
function.
if ($('#dropInContainer').data('enabled')) { // Digital River - if enabled no cvv needed thus pictures are always shown instead of input
$('#savedPaymentNotSelectedMessage').hide();
} else {
$('.saved-payment-instrument .card-image').removeClass('checkout-hidden');
$('.saved-payment-instrument .security-code-input').addClass('checkout-hidden');
$('.saved-payment-instrument.selected-payment'
+ ' .card-image').addClass('checkout-hidden');
$('.saved-payment-instrument.selected-payment '
+ '.security-code-input').removeClass('checkout-hidden');
}

Add the following code to the addNewPaymentInstrument
function:
// Digital River Drop-in section
if ($('#dropInContainer').data('enabled')) {
$('.drop-in-container').removeClass('checkout-hidden'); // show drop-in form to enter new payment
$('.submit-payment').addClass('digitalriver-hide'); // next step will be launched from drop-in button instead
}
Add the following code to the canceNewPayment
function:
// Digital River Drop-in section
if ($('#dropInContainer').data('enabled')) {
$('.submit-payment').removeClass('digitalriver-hide');
$('.drop-in-container').addClass('checkout-hidden');
}

Summary script
Use the summary.js
script to extend the checkout script functionality that provides order summary information. For more information on this script and how it is used with Customer Credit, refer to Use Customer Credit and Customize the Customer Credit Cartridge.
Script path: cartridge/client/default/js/checkout/summary.js
Add summary info rows (highlighted section).
/if (totals.drTaxDiscountTotal && Math.abs(totals.drTaxDiscountTotal.value) >= 0.01) {
$('.vat-included-msg').addClass('hidden');
$('.vat-exempted-msg').removeClass('hidden');
$('.vat-exempted-value').text(totals.drTaxDiscountTotal.formatted);
} else {
$('.vat-included-msg').removeClass('hidden');
$('.vat-exempted-msg').addClass('hidden');
}

Add rows (highlighted section).
if (totals.isImporterOfRecordTax || (totals.duty && totals.duty.value > 0)) {
$('.duty-item').removeClass('hide-order-discount');
$('.duty-total').text(totals.duty.formatted);
} else {
$('.duty-item').addClass('hide-order-discount');
}
if (totals.isImporterOfRecordTax || (totals.importerTax && totals.importerTax.value > 0)) {
$('.importerTax-item').removeClass('hide-order-discount');
$('.importerTax-total').text(totals.importerTax.formatted);
} else {
$('.importerTax-item').addClass('hide-order-discount');
}
if (totals.isImporterOfRecordTax || (totals.totalFees && totals.totalFees.value > 0)) {
$('.totalFees-item').removeClass('hide-order-discount');
$('.totalFees-total').text(totals.totalFees.formatted);
} else {
$('.totalFees-item').addClass('hide-order-discount');
}

Added the following code:
if (totals.isZeroTotal) {
$('.payment-instruments-container').data('payment-method-id', 'DIGITAL_RIVER_ZERO_PAYMENT');
$('#payment-method-input').val('DIGITAL_RIVER_ZERO_PAYMENT');
$('#headingPayment').addClass('digitalriver-hide');
$('.submit-payment').removeClass('digitalriver-hide');
} else {
$('.payment-instruments-container').data('payment-method-id', 'DIGITAL_RIVER_DROPIN');
$('#payment-method-input').val('DIGITAL_RIVER_DROPIN');
$('#headingPayment').removeClass('digitalriver-hide');
if (!$('.submit-payment').data('saved-payments')) {
$('.submit-payment').addClass('digitalriver-hide');
}
}

Shipping script
Use the shipping.js
script to extend the checkout functionality.
Script path: cartridge/client/default/js/checkout/shipping.js
Added the required references.
'use strict";
var addressHelpers = require('base/checkout/address');
........
var addressHelpers = require('base/checkout/shipping');

Modified the updateMultiShipInformation
function with the following condition on line 20:
if (!$drCustomerError.length) {
$submitShippingBtn.prop('disabled', null);
}

The shipping.js
script has been updated so that the Ajax call to load shipping methods is not triggered if a shopper successfully submits a payment.
Wrap the Ajax call in an if
statement as follows:
if(!$('.DR-place-order').data('dr-redirect-success'))
{
… code to load shipping methods
}

The updateShippingList function has been extended to handle trigger updates to the shipping methods list if the Zip Code is changed in the shipping form. The handler code has been moved to a separate function that triggers both the stateCode and shippingZipCode fields in the shipping form.
var handler = function (e) {
if (baseObj.methods && baseObj.methods.updateShippingMethodList) {
baseObj.methods.updateShippingMethodList($(e.currentTarget.form));
} else {
updateShippingMethodList($(e.currentTarget.form));
}
};
$(document).on('focusout', '.shippingZipCode', handler);
$('select[name$="shippingAddress_addressFields_states_stateCode"]')
.on('change', handler);

SFRA model changes
This section describes changes applied to the most recent models provided with the cartridge. In addition, the section describes changes you may need to make the models support your specific use cases and integration choices. The following scripts were updated and their changes are described in this section:
Shipping
The Shipping (shipping.js
) model was updated to add support for the Digital River Shipping Options feature.
Model path: cartridge/models/shipping.js
The following functions were extended in this model:
getSelectedShippingMethod
ShippingModel
The getSelectedShippingMethod
function was extended as follows:
function getSelectedShippingMethod(shipment) {
if (!shipment) return null;
var method = shipment.shippingMethod;
// Digital River Modification - Begin
var model = null;
if (method) {
model = new ShippingMethodModel(method, shipment);
if (method.ID.indexOf('DRDefaultShp') > -1) {
var sqModel = {};
sqModel.default = false;
sqModel.description = shipment.custom.drSQDescription;
sqModel.displayName = shipment.custom.drSQServiceLevel;
sqModel.shippingTerms = shipment.custom.drSQShippingTerms;
sqModel.estimatedArrivalTime = shipment.custom.drSQEstimatedArrivalTime;
sqModel.ID = shipment.custom.drUniqueID;
sqModel.selected = true;
sqModel.shippingCost = shipment.custom.drSQTotalAmount;
model = sqModel;
}
}
// Digital River Modification - End
return model;
}
The ShippingModel
function was extended as follows:
function ShippingModel(shipment, address) {
parent.apply(this, arguments);
var shippingHelpers = require('*/cartridge/scripts/checkout/shippingHelpers');
var basketCalculationHelpers = require('*/cartridge/scripts/helpers/basketCalculationHelpers');
var Transaction = require('dw/system/Transaction');
this.applicableShippingMethods = shippingHelpers.getApplicableShippingMethods(shipment, address);
if (!this.applicableShippingMethods || empty(this.applicableShippingMethods)) {
var basket = BasketMgr.getCurrentBasket();
if (basket) {
Transaction.wrap(function () {
shipment.setShippingMethod(null);
basketCalculationHelpers.calculateTotals(basket);
});
}
}
this.selectedShippingMethod = getSelectedShippingMethod(shipment);
}

SFRA script changes
This section describes the changes applied to the most recent cartridge scripts. In addition, you may need to make some of these changes to the scripts to support your specific use cases and integration choices. The following scripts and their changes are described in this section:
Shipping Helpers
The Shipping Helpers (shippingHelpers.js
) script has been updated to add support for the Digital River shipping options feature.
Script path: cartridge/scripts/checkout/shippingHelpers.js
The following functions were extended in this script:
getFirstApplicableShippingMethod
This method was changed as follows:
function getFirstApplicableShippingMethod(methods, filterPickupInStore) {
var method;
var iterator = methods.iterator();
while (iterator.hasNext()) {
method = iterator.next();
if (!filterPickupInStore) {
if (method.apiShippingMethod) {
if (!method.apiShippingMethod.custom.storePickupEnabled) {
break;
}
} else if (!method.custom.storePickupEnabled) {
break;
}
}
}
return method;
}
ensureShipmentHas method
This method was changed as follows:
output.ensureShipmentHasMethod = function (shipment) {
var shippingMethod = shipment.shippingMethod;
if (!shippingMethod) {
var methods = ShippingMgr.getShipmentShippingModel(shipment).applicableShippingMethods;
var defaultMethod = ShippingMgr.getDefaultShippingMethod();
var isAllDigitalProducts = checkoutHelper.checkDigitalProductsOnly(shipment.productLineItems);
if (isAllDigitalProducts) {
shippingMethod = collections.find(methods, function (method) {
return method.ID.includes('DigitalProductShipment');
});
} else if (!defaultMethod) {
// If no defaultMethod set, just use the first one
shippingMethod = getFirstApplicableShippingMethod(methods, true);
} else {
// Look for defaultMethod in applicableMethods
shippingMethod = collections.find(methods, function (method) {
return method.ID === defaultMethod.ID;
});
}
// If found, use it. Otherwise return the first one
if (!shippingMethod && methods && methods.length > 0) {
shippingMethod = getFirstApplicableShippingMethod(methods, true);
}
if (shippingMethod) {
shipment.setShippingMethod(shippingMethod);
}
}
};
getApplicableShipping method
This method was changed as follows:
function getApplicableShippingMethods(shipment, address) {
if (!shipment) return null;
var shipmentShippingModel = ShippingMgr.getShipmentShippingModel(shipment);
var shippingMethods;
if (address) {
shippingMethods = shipmentShippingModel.getApplicableShippingMethods(address);
} else {
shippingMethods = shipmentShippingModel.getApplicableShippingMethods();
}
// Filter out whatever the method associated with in store pickup
var convertedMethods = checkoutHelper.convertShippingMethodsToModel(shippingMethods, shipment);
var modifiedShippingQuotes = [];
var checkoutPageOnly = checkoutHelper.isAllowedEndpoint();
if (checkoutPageOnly) {
var shippingQuotes = ShippingQuotesHelper.getShippingQuotes(shippingMethods, shipment, convertedMethods.drShippingMethod);
modifiedShippingQuotes = ShippingQuotesHelper.modifyShippingQuotes(shippingQuotes);
}
var isAllDigitalProducts = checkoutHelper.checkDigitalProductsOnly(shipment.productLineItems);
var drEnabled = Site.getCurrent().getCustomPreferenceValue('drUseDropInFeature');
var drShippingMethodAvailability = Site.getCurrent().getCustomPreferenceValue('drShippingMethodAvailability').value;
if (!drEnabled || (isAllDigitalProducts && drShippingMethodAvailability === 'quotes')) {
modifiedShippingQuotes = convertedMethods.filteredMethods;
}
var bothShippingMethods = convertedMethods.filteredMethods;
if (drEnabled) {
bothShippingMethods = convertedMethods.filteredMethods.concat(modifiedShippingQuotes);
}
var shippingMethodAvailability = null;
switch (drShippingMethodAvailability) {
case 'native':
shippingMethodAvailability = convertedMethods.filteredMethods;
break;
case 'quotes':
shippingMethodAvailability = modifiedShippingQuotes;
break;
case 'both':
shippingMethodAvailability = bothShippingMethods;
break;
default:
shippingMethodAvailability = convertedMethods.filteredMethods;
break;
}
return shippingMethodAvailability;
}
output.getApplicableShippingMethods = getApplicableShippingMethods;
selectShippingMethod
This method was changed as follows:
output.selectShippingMethod = function (shipmentArg, shippingMethodID, shippingMethods, address) {
var shipment = shipmentArg;
var applicableShippingMethods;
var defaultShippingMethod = ShippingMgr.getDefaultShippingMethod();
var shippingAddress;
if (address && shipment) {
shippingAddress = shipment.shippingAddress;
if (shippingAddress) {
if (address.stateCode && shippingAddress.stateCode !== address.stateCode) {
shippingAddress.stateCode = address.stateCode;
}
if (address.postalCode && shippingAddress.postalCode !== address.postalCode) {
shippingAddress.postalCode = address.postalCode;
}
}
}
var isShipmentSet = false;
if (shippingMethods) {
applicableShippingMethods = checkoutHelper.convertShippingMethodsToModel(shippingMethods, shipment).filteredMethods;
} else {
applicableShippingMethods = getApplicableShippingMethods(shipment, address);
}
applicableShippingMethods = new ArrayList(applicableShippingMethods);
if (shippingMethodID) {
// loop through the shipping methods to get shipping method
var iterator = applicableShippingMethods.iterator();
while (iterator.hasNext()) {
var shippingMethod = iterator.next();
if (shippingMethod.ID === shippingMethodID) {
shipment.setShippingMethod(shippingMethod.apiShippingMethod);
isShipmentSet = true;
checkoutHelper.populateShipmentCustomPref(shipment, shippingMethod);
break;
}
}
}
if (!isShipmentSet) {
var isAllDigitalProducts = checkoutHelper.checkDigitalProductsOnly(shipment.productLineItems);
if (isAllDigitalProducts) {
var digitalShippingMethod = collections.find(applicableShippingMethods, function (method) {
return method.ID.includes('DigitalProductShipment');
});
shipment.setShippingMethod(digitalShippingMethod.apiShippingMethod);
} else if (collections.find(applicableShippingMethods, function (sMethod) {
return sMethod.ID === defaultShippingMethod.ID;
})) {
shipment.setShippingMethod(defaultShippingMethod);
} else if (applicableShippingMethods.length > 0) {
var firstMethod = getFirstApplicableShippingMethod(applicableShippingMethods, true);
shipment.setShippingMethod(firstMethod.apiShippingMethod);
checkoutHelper.populateShipmentCustomPref(shipment, firstMethod);
} else {
shipment.setShippingMethod(null);
}
}
};
Calculate hook
The Calculate hook (calculate.js
) script was updated to add support for the Dynamic Shipping and Digital River taxation features.
Script path: cartridge/scripts/hooks/cart/calculate.js
The following functions were extended:
calculateShipping
This function has changed as follows:
output.calculateShipping = function (basket) {
var status = new Status(Status.OK);
try {
Transaction.wrap(function () {
for (let i = 0; i < basket.shipments.length; i++) {
var shipment = basket.shipments[i];
if (shipment && shipment.shippingMethodID && shipment.shippingMethodID.indexOf('DRDefaultShp') > -1) {
ShippingMgr.applyShippingCost(basket);
var priceStringified = shipment.custom.drSQTotalAmount;
var shippingPrice;
if (priceStringified) {
if (shipment.shippingMethodID === 'DRDefaultShpJPY') {
shippingPrice = parseFloat(priceStringified.replace(/[^\d.]/g, ''));
} else {
shippingPrice = Number(priceStringified.replace(/[^0-9,.-]+/g, '').replace(',', '.'));
}
var shippingLineItem = shipment.shippingLineItems[0];
shippingLineItem.setPriceValue(shippingPrice);
var taxRate = shippingLineItem.getTaxRate();
var updatedTax = shippingPrice * taxRate;
shippingLineItem.updateTaxAmount(new dw.value.Money(updatedTax, basket.currencyCode));
}
} else {
ShippingMgr.applyShippingCost(basket);
}
}
basket.updateTotals();
});
} catch (e) {
status = new Status(Status.ERROR);
}
return status;
};
calculateTax
This function has changed as follows:
output.calculateTax = function (basket) {
var DigitalRiverEnabled = require('dw/system/Site').getCurrent().getCustomPreferenceValue('drUseDropInFeature');
var checkoutData = taxHelper.parseCheckoutData(basket.custom.drCheckoutData);
if (!DigitalRiverEnabled || !checkoutData) { // use default tax calculation if Digital River is disabled or checkout hasn't been created yet
return parent.calculateTax.apply(null, arguments);
}
var logger = require('*/cartridge/scripts/digitalRiver/drLogger').getLogger('digitalriver.checkout');
if (!taxHelper.checkoutDataIsValid(checkoutData, basket)) {
return parent.calculateTax.apply(null, arguments);
}
// taxHelper.updateTaxPromotion(basket, checkoutData);
var currency = basket.getCurrencyCode();
var shippingTaxAmount = checkoutData.shippingTax;
var itemNavigation = checkoutData.items.map(function (item) { return item.digitalRiverID; });
var lineItems = basket.getAllLineItems();
collections.forEach(lineItems, function (lineItem) {
if (lineItem instanceof dw.order.ProductLineItem) { // updating taxes for product line items
var taxAmount = 0;
var taxRate = 0;
if (!lineItem.optionProductLineItem) { // option products line items will have 0 taxes while parent item taxes will include options cost
var sourceItem = checkoutData.items[itemNavigation.indexOf(lineItem.custom.digitalRiverID)];
if (sourceItem) {
taxAmount = sourceItem.tax.amount;
taxRate = sourceItem.tax.rate;
}
}
if (!empty(taxRate)) {
lineItem.updateTax(taxRate);
}
lineItem.updateTaxAmount(new Money(taxAmount, currency));
} else if (lineItem instanceof dw.order.ShippingLineItem
&& lineItem.ID !== 'digitalRiver_duty'
&& lineItem.ID !== 'digitalRiver_importerTax'
) {
lineItem.updateTaxAmount(new Money(shippingTaxAmount, currency));
} else if (lineItem instanceof dw.order.PriceAdjustment) { // price adjustments except Fees will have 0 taxes
if (!taxHelper.isDrAdjustment(lineItem.promotionID)) {
lineItem.updateTax(0);
}
} else {
lineItem.updateTax(0);
logger.warn('Taxes for {0} set to zero. Digital River tax calculation is not supported by this implementation', lineItem.constructor.name);
}
});
return new Status(Status.OK);
};
SFRA JSON files changes
This section describes the changes made to the most recent cartridge JSON. The section also describes changes that you may need to make to the scripts to support your specific use cases and integration choices.
The following scripts and their changes are described in this section:
Hooks
The Hooks JSON configuration (hooks.json
) was updated with the addition of the following hooks:
dw.order.calculateTax
dw.order.calculateShipping
app.payment.processor.digital_river
app.payment.processor.digital_river_dropin
app.payment.form.processor.digital_river
app.payment.processor.digital_river_paypal
app.payment.form.processor.digital_river_paypal
app.payment.form.processor.digital_river_dropin
app.payment.form.processor.digital_river_zero_payment
app.payment.processor.digital_river_zero_payment
dw.system.request.onRequest
JSON location: cartridges/int_digitalriver_sfra/hooks.json
The JSON configuration is:
{
"hooks": [
{
"name": "dw.order.calculateTax",
"script": "./cartridge/scripts/hooks/cart/calculate.js"
},
{
"name": "dw.order.calculateShipping",
"script": "./cartridge/scripts/hooks/cart/calculate.js"
},
{
"name": "app.payment.processor.digital_river",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river"
},
{
"name": "app.payment.processor.digital_river_dropin",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_dropin"
},
{
"name": "app.payment.form.processor.digital_river",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_form_processor"
},
{
"name": "app.payment.processor.digital_river_paypal",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_paypal"
},
{
"name": "app.payment.form.processor.digital_river_paypal",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_paypal_form_processor"
},
{
"name": "app.payment.form.processor.digital_river_dropin",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_dropin_form_processor"
},
{
"name": "app.payment.form.processor.digital_river_zero_payment",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_zero_payment_form_processor"
},
{
"name": "app.payment.processor.digital_river_zero_payment",
"script": "./cartridge/scripts/hooks/payment/processor/digital_river_zero_payment"
},
{
"name": "dw.system.request.onRequest",
"script": "./cartridge/scripts/hooks/request"
}
]
}

Countries
You need to update the Countries JSON configuration (countries.json
) by adding alternativeCurrencyCodes to each country defined in the JSON.
JSON location: cartridge/config/countries.json
The alternative countries array should contain all currency codes that will be used with the Dynamic Pricing feature.
For example:
{
"id": "en_US",
"currencyCode": "USD",
"alternativeCurrencyCodes":["EUR","GBP","AUD","CAD","JPY","CNY","KRW","NOK","TRY"]
}
Drop-in data handlers
Use the cartridge's Digital River API endpoints and Digital River Drop-in payments external script to handle client payments. The DigitalRiver.http.service shares one profile and one credential.
dropinHelper.js
Use the dropinHelper.js
to configure specific payment data used by Drop-in payments.
Path: int_digitalriver\cartridge\scripts\digitalRiver\dropinHelper.js
You can use use the following elements in dropinHelper.js to configure the payment data.
switch(source.type)
—saves payment data (for example,creditCard
) from Drop-in payments to the customer's wallet where it can be used the next time the customer goes to the Checkout page.switch(paymentType)
—returns object with specific fields from the SFCCpaymentInstrument
object.switch(source.type)
—returns object with specific fields from the Drop-In response object.
payment.js
Use the payments.js
script to add specific attributes to the core payment object. Templates and client-side scripts (JSON) use this object.
Path: int_digitalriver_sfra\cartridge\models\payment.js
extendDigitalRiverInfo
—extract data (such aspaymentType
,maskedCreditCardNumber
, and so on) and makes it available in templates or the client-side as JSON.
Webhook handler
The webhook handler consists of a controller, a helper script, and a handler script.
Endpoint URL structure:
{sandbox}/{site}/{locale}/HooksObserver-Debug
Example of endpoint URL:
https://zzrk-032.sandbox.us01.dx.commercecloud.salesforce.com/on/demandware.store/Sites-DR-SFRA-Net-Site/en_US/HooksObserver-Debug
Controller
Use the following steps to use the controller portion of the handler:
File: cartridges/int_digitalriver/cartridge/controllers/HooksObserver.js
Step 1: Catch request data
// get the hook data
var drSignature = request.getHttpHeaders().get('digitalriver-signature') || '';
var requestBodyAsString = request.httpParameterMap.getRequestBodyAsString() || '{}';
var hook = JSON.parse(requestBodyAsString);
var hookType = hook.type ? hook.type : 'error';
var hookHandlerResponse;
var checkSignature = drHooksHelper.checkSignature(drSignature, requestBodyAsString);
Step 2: Use helper script to log request data
// log info
var DRLogger = logger.getLogger("drWebhooks", hookType); // hook type, e.g. refund.pending
DRLogger.info(requestBodyAsString);
Step 3: Check the signature and handle hook
// check signature
if (checkSignature.error) {
hookType = 'error';
DRLogger.error(checkSignature.errorMessage);
// handle hook with error log
hookHandlerResponse = drHooksHandler.default(hookType, hook.data);
} else if (drHooksHandler[hookType]) { // signature is ok, handle hooks
hookHandlerResponse = drHooksHandler[hookType](hook.data);
} else {
hookHandlerResponse = drHooksHandler.default(hookType, hook.data);
}
Step 4: Send an email
Individual handlers, not the controller, can optionally send an email.
// email section
drHooksHelper.sendTechnicalMail(hookType + ' request')
Step 5: Return the response
// response
response.setStatus(hookHandlerResponse.statusCode);
Helper script
Use the helper script part of the controller to:
Send an email (
sendTechnicalMail(title, content)
).Set the email address in Business Manager site preferences

File: cartridges/int_digitalriver/cartridge/scripts/digitalRiver/hooksObserver/drHooksHelper.js
Handler script
Use the handler script where custom handling is needed. The handler script contains simple examples of handlers, which can be modified and extended for custom handling. Examples are provided for the following event types:
refund.pending
refund.complete
If the hookType
(webhook's event type) is set in the request body (see Step 1: Catch request data in Controller), the handler script handles events by the event type and returns a 200 OK
response.

If a handler with a selected hookType
does not exist, or if the hookType
is not set, the “default” handler is used and processes all other types and returns 200
response code.
If an error occurs, the handler returns a 500
response code.
To add a handler for a specific event type, create a function with a data attribute that returns a response code (for example, 200
) and add a function name to the module.exports
object.

File: cartridges/int_digitalriver/cartridge/scripts/digitalRiver/hooksObserver/drHooksHandler.js
Creating webhooks
To receive a notification when an event occurs (for example, when a refund occurs), create a webhook for that event in the Digital River Dashboard.
Sign in to the Digital River Dashboard.
Click Webhooks.
Create one webhook per site and configure each webhook to send one or more events using your production or test environment.
Last updated