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')