Handling Custom Shipping and Payment Methods in SFCC


salesforce commerce cloud
      
          const sitePreferences : SitePreferences = dw.system.Site.getCurrent().getPreferences();
const practiceApiName : String = sitePreferences.getCustom()["practiceApiName"];
        
      
          'use strict';
/**
 * Verifies the required information for billing form is provided.
 * @param {Object} req - The request object
 * @param {Object} paymentForm - the payment form
 * @param {Object} viewFormData - object contains billing form data
 * @returns {Object} an object that has error information or payment information
 */

function processForm(req, paymentForm, viewFormData) {
  viewFormData.paymentMethod = {
    value: paymentForm.paymentMethod.value,
    htmlName: paymentForm.paymentMethod.value
  };
  viewFormData.paymentInformation = {
    cardType: {
      value: paymentForm.creditCardFields.cardType.value,
      htmlName: paymentForm.creditCardFields.cardType.htmlName
    },
    cardNumber: {
      value: paymentForm.creditCardFields.cardNumber.value,
      htmlName: paymentForm.creditCardFields.cardNumber.htmlName
    },
    securityCode: {
      value: paymentForm.creditCardFields.securityCode.value,
      htmlName: paymentForm.creditCardFields.securityCode.htmlName
    },
    expirationMonth: {
      value: parseInt(paymentForm.creditCardFields.expirationMonth.selectedOption, 10),
      htmlName: paymentForm.creditCardFields.expirationMonth.htmlName
    },
    expirationYear: {
      value: parseInt(paymentForm.creditCardFields.expirationYear.value, 10),
      htmlName: paymentForm.creditCardFields.expirationYear.htmlName
    }
  }

  if (req.form.storedPaymentUUID) {
    viewData.storedPaymentUUID = req.form.storedPaymentUUID;
  }

  viewData.saveCard = paymentForm.creditCardFields.saveCard.checked;

  // process payment information
  if (viewData.storedPaymentUUID
    && req.currentCustomer.raw.authenticated
    && req.currentCustomer.raw.registered
  ) {
    let paymentInstruments = req.currentCustomer.wallet.paymentInstruments;
    let paymentInstrument = array.find(paymentInstruments, function (item) {
      return viewData.storedPaymentUUID === item.UUID;
    });

    viewData.paymentInformation.cardNumber.value = paymentInstrument.creditCardNumber;
    viewData.paymentInformation.cardType.value = paymentInstrument.creditCardType;
    viewData.paymentInformation.securityCode.value = req.form.securityCode;
    viewData.paymentInformation.expirationMonth.value = paymentInstrument.creditCardExpirationMonth;
    viewData.paymentInformation.expirationYear.value = paymentInstrument.creditCardExpirationYear;
    viewData.paymentInformation.creditCardToken = paymentInstrument.raw.creditCardToken;
  }

  return {
    error: false,
    viewData: viewFormData
  };
}

exports.processForm = processForm;
        
      
          'use strict';

const Transaction = require('dw/system/Transaction');

/**
 * Creates a token. This should be replaced by utilizing a tokenization provider
 * @returns {string} a token
 */

function createToken() {
  return Math.random().toString(36).substr(2);
}

/**
 * Verifies that entered credit card information is a valid card. If the information is valid a
 * credit card payment instrument is created
 * @param {dw.order.Basket} basket Current users's basket
 * @param {Object} paymentInformation - the payment information
 * @return {Object} returns an error object
 */

function Handle(basket, paymentInformation) {
  const collections = require('*/cartridge/scripts/util/collections');
  let currentBasket = basket;
  let cardErrors = {};
  let cardNumber = paymentInformation.cardNumber.value;
  let cardSecurityCode = paymentInformation.securityCode.value;
  let expirationMonth = paymentInformation.expirationMonth.value;
  let expirationYear = paymentInformation.expirationYear.value;
  let serverErrors = [];
  let creditCardStatus;
  let cardType = paymentInformation.cardType.value;
  let paymentCard = PaymentMgr.getPaymentCard(cardType);

  if (!paymentInformation.creditCardToken) {
    if (paymentCard) {
      creditCardStatus = paymentCard.verify(
        expirationMonth,
        expirationYear,
        cardNumber,
        cardSecurityCode
      );
    } else {
      cardErrors[paymentInformation.cardNumber.htmlName] =
        Resource.msg('error.invalid.card.number', 'creditCard', null);
      return { fieldErrors: [cardErrors], serverErrors: serverErrors, error: true };
    }

    if (creditCardStatus.error) {
      collections.forEach(creditCardStatus.items, function (item) {
        switch (item.code) {
          case PaymentStatusCodes.CREDITCARD_INVALID_CARD_NUMBER:
            cardErrors[paymentInformation.cardNumber.htmlName] =
              Resource.msg('error.invalid.card.number', 'creditCard', null);
            break;

          case PaymentStatusCodes.CREDITCARD_INVALID_EXPIRATION_DATE:
            cardErrors[paymentInformation.expirationMonth.htmlName] =
              Resource.msg('error.expired.credit.card', 'creditCard', null);
            cardErrors[paymentInformation.expirationYear.htmlName] =
              Resource.msg('error.expired.credit.card', 'creditCard', null);
            break;

          case PaymentStatusCodes.CREDITCARD_INVALID_SECURITY_CODE:
            cardErrors[paymentInformation.securityCode.htmlName] =
              Resource.msg('error.invalid.security.code', 'creditCard', null);
            break;

          default:
            serverErrors.push(Resource.msg('error.card.information.error', 'creditCard', null));
        }
      });

      return { fieldErrors: [cardErrors], serverErrors: serverErrors, error: true };
    }
  }

  Transaction.wrap(function () {
    let paymentInstruments = currentBasket.getPaymentInstruments(
      'practice_payment_card'
    );

    collections.forEach(paymentInstruments, function (item) {
      currentBasket.removePaymentInstrument(item);
    });

    let paymentInstrument = currentBasket.createPaymentInstrument(
      'practice_payment_card', currentBasket.totalGrossPrice
    );

    paymentInstrument.setCreditCardHolder(currentBasket.billingAddress.fullName);
    paymentInstrument.setCreditCardNumber(paymentInformation.cardNumber.value);
    paymentInstrument.setCreditCardType(paymentInformation.cardType.value);
    paymentInstrument.setCreditCardExpirationMonth(paymentInformation.expirationMonth.value);
    paymentInstrument.setCreditCardExpirationYear(paymentInformation.expirationYear.value);
    paymentInstrument.setCreditCardToken(
      paymentInformation.creditCardToken
        ? paymentInformation.creditCardToken
        : createToken()
    );
  });

  return { fieldErrors: null, serverErrors: null, error: false };
}

/**
 * Authorizes a payment using a credit card. Customizations may use other processors and custom
 *      logic to authorize credit card payment.
 * @param {number} orderNumber - The current order's number
 * @param {dw.order.PaymentInstrument} paymentInstrument -  The payment instrument to authorize
 * @param {dw.order.PaymentProcessor} paymentProcessor -  The payment processor of the current
 *      payment method
 * @return {Object} returns an error object
 */

function Authorize(orderNumber, paymentInstrument, paymentProcessor) {
  const Resource = require('dw/web/Resource');
  let serverErrors = [];
  let fieldErrors = {};
  let error = false;

  try {
    Transaction.wrap(function () {
      paymentInstrument.paymentTransaction.setTransactionID(orderNumber);
      paymentInstrument.paymentTransaction.setPaymentProcessor(paymentProcessor);
    });
  } catch (e) {
    error = true;
    serverErrors.push(
      Resource.msg('error.technical', 'checkout', null)
    );
  }

  return { fieldErrors: fieldErrors, serverErrors: serverErrors, error: error };
}

exports.Handle = Handle;
exports.Authorize = Authorize;
exports.createToken = createToken;
        
      
          {
  "hooks": [
    {
      "name": "app.payment.processor.practice_payment_card",
      "script": "./cartridge/scripts/hooks/payment/processor/practice_payment_card"
    },
    {
      "name": "app.payment.form.processor.practice_payment_card",
      "script": "./cartridge/scripts/hooks/payment/processor/practice_payment_card_form_processor"
    }
  ]
}
        
      
          {
    "hooks": "./hooks.json"
}
        
      
          <!--- applicable credit cards--->
<div class="tab-pane ${'credit-card-content-' + paymentOption.ID} ${loopstatus.first ? ' active' : ''}"
    id="${'credit-card-content-' + paymentOption.ID}" role="tabpanel">
    <fieldset class="payment-form-fields">
        <!--- payment method is credit card --->
        <input type="hidden" class="form-control"
            name="${pdict.forms.billingForm.paymentMethod.htmlName}"
            value="${paymentOption.ID}">

        <!--- register/authenticated user --->
        <isif condition="${pdict.customer.registeredUser}">
            <div
                class="user-payment-instruments container ${pdict.customer.registeredUser && pdict.customer.customerPaymentInstruments.length ? '' : 'checkout-hidden'}">

                <!--- Stored user payments --->
                <div class="stored-payments">
                    <isinclude template="checkout/billing/storedPaymentInstruments" />
                </div>

                <!--- Add new payment card button --->
                <div class="row">
                    <button class="btn btn-block add-payment btn-outline-primary" type="button">${Resource.msg('button.add.payment', 'checkout', null)}</button>
                </div>
            </div>
        </isif>

        <fieldset class="credit-card-form ${pdict.customer.registeredUser && pdict.customer.customerPaymentInstruments.length ? 'checkout-hidden' : ''}">
            <isinclude template="checkout/billing/creditCardForm" />
            <isif condition="${pdict.customer.registeredUser}">
                <button
                    class="btn btn-block cancel-new-payment btn-outline-primary ${pdict.customer.registeredUser && pdict.customer.customerPaymentInstruments.length ? '' : 'checkout-hidden'}"
                    type="button">${Resource.msg('button.back.to.stored.payments', 'checkout', null)}</button>
            </isif>
        </fieldset>
    </fieldset>
</div>
        
      
          <li class="nav-item" data-method-id="${paymentOption.ID}">
    <isscript>
        let classForCardTab = 'nav-link credit-card-tab' + (loopstatus.first ? ' active' : '');
    </isscript>
    <a class="${classForCardTab}" data-toggle="tab" href="${'#credit-card-content-' + paymentOption.ID}" role="tab">
        <img class="credit-card-option"
              src="${URLUtils.staticURL('/images/credit.png')}"
              height="32"
              alt="${paymentOption.name}"
              title="${paymentOption.name}"
        >
    </a>
</li>
        
      
          <isloop items="${pdict.order.billing.payment.applicablePaymentMethods}" var="paymentOption" status="loopstatus">
    <isif condition="${paymentOption.ID === 'CREDIT_CARD' || paymentOption.ID === 'practice_payment_card'}">
        <isinclude template="checkout/billing/paymentOptions/creditCardContent" />
    </isif>
</isloop>
        
      
          <isloop items="${pdict.order.billing.payment.applicablePaymentMethods}" var="paymentOption" status="loopstatus">
    <isif condition="${paymentOption.ID === 'CREDIT_CARD' || paymentOption.ID === 'practice_payment_card'}">
        <isinclude template="checkout/billing/paymentOptions/creditCardTab" />
    </isif>
</isloop>
        
      
          <div class="payment-details">
    <isloop items="${pdict.order.billing.payment.selectedPaymentInstruments}" var="payment">
        <isif condition="${paymentOption.ID === 'CREDIT_CARD' || paymentOption.ID === 'practice_payment_card'}">
            <isinclude template="checkout/billing/paymentOptions/creditCardSummary" />
        </isif>
    </isloop>
</div>
        
      
          if ($('.payment-information').data('payment-method-id') === 'CREDIT_CARD'
                        || $('.payment-information').data('payment-method-id') === 'practice_payment_card')
        
blog author

Stefan Stanković

Software Developer