diff --git a/README.md b/README.md index 1d73500..871e7f1 100755 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ * **Description:** Cybersource, a Visa solution, is the only global, modular payment management platform built on secure Visa infrastructure with the payment reach and fraud insights of a massive $500B+ global processing network. You can find out more about what Cybersource does [here](https://www.cybersource.com/en-gb.html) * **Categories:** Payment Processing, Fraud Detection, Address Validation, Tax Computation -* **Version:** 24.2.1 -* **Last Certification Date:** May 2024 -* **Supports SFRA v6.3.0** +* **Version:** 24.3.0 +* **Last Certification Date:** August 2024 +* **Supports SFRA v7.0** * **JavaScript Controllers Friendly:** **YES** ### Contact ### diff --git a/cartridges/int_cybs_sfra/cartridge/client/default/custom/js/flexMicroform.js b/cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js similarity index 76% rename from cartridges/int_cybs_sfra/cartridge/client/default/custom/js/flexMicroform.js rename to cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js index 2a57724..3fd6658 100644 --- a/cartridges/int_cybs_sfra/cartridge/client/default/custom/js/flexMicroform.js +++ b/cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js @@ -31,12 +31,28 @@ $(document).ready(function () { var number = microform.createField('number'); var securityCode = microform.createField('securityCode'); securityCode.load('#securityCode-container'); + number.load('#cardNumber-container'); number.on('change', function (data) { + if (data.couldBeValid) { + $('.card-number-wrapper .invalid-feedback').css('display', 'none'); + } var cardType = data.card[0].name; $('.card-number-wrapper').attr('data-type', cardType); $('#cardType').val(cardType); }); + + + $('#expirationMonth').on('change', function (event) { + $('#expirationMonthMissingMessage').css('display', 'none'); + $('#expiredCardMessage').css('display', 'none'); + }) + $('#expirationYear').on('change', function (event) { + $('#expirationYearMissingMessage').css('display', 'none'); + $('#expiredCardMessage').css('display', 'none'); + }) + + /** * * * @param {*} token * @@ -60,9 +76,6 @@ $(document).ready(function () { var expMonth = $('#expirationMonth').val(); var expYear = $('#expirationYear').val(); - if (expMonth === '' || expYear === '') { - return false; - } // Send in optional parameters from other parts of your payment form var options = { expirationMonth: expMonth.length === 1 ? '0' + expMonth : expMonth, @@ -76,11 +89,21 @@ $(document).ready(function () { microform.createToken(options, function (err, response) { // At this point the token may be added to the form // as hidden fields and the submission continued + let flag = false; if (err) { $('.card-number-wrapper .invalid-feedback').text(err.message).css('display', 'block'); + flag = true; + } + + if (!cardExpiryValidate()) { + flag = true; + } + + if (flag == true) { return true; } + var decodedJwt = parseJwt(response); document.getElementById('cardNumber').valid = true; $('#flex-response').val(response); @@ -128,6 +151,34 @@ $(document).ready(function () { } } + function cardExpiryValidate() { + var expMonth = $('#expirationMonth').val(); + var expYear = $('#expirationYear').val(); + + if (expMonth == '' || expYear == '') { + if (expMonth == '') { + $('#expirationMonthMissingMessage').css('display', 'block'); + } + if (expYear == '') { + $('#expirationYearMissingMessage').css('display', 'block'); + } + return false; + } + else { + let currentDate = new Date(); + let currentMonth = currentDate.getMonth() + 1; + let currentYear = currentDate.getFullYear(); + + // Check if the card is expired + if (expYear < currentYear || (expYear == currentYear && expMonth < currentMonth)) { + $('#expiredCardMessage').css('display', 'block'); + return false; + } + } + return true; + } + + $('.payment-summary .edit-button').on('click', function () { $('#flex-response').val(''); }); diff --git a/cartridges/int_cybs_sfra/cartridge/client/default/js/checkout/checkout.js b/cartridges/int_cybs_sfra/cartridge/client/default/js/checkout/checkout.js index bc988be..d084ef7 100644 --- a/cartridges/int_cybs_sfra/cartridge/client/default/js/checkout/checkout.js +++ b/cartridges/int_cybs_sfra/cartridge/client/default/js/checkout/checkout.js @@ -104,5 +104,19 @@ $('#checkout-main').on('click', '.next-step-button button', function (event) { }); } }); - +$('#applePayPaymentOptionLink').on('click', function (event) { + $('#placeOrderButton').hide(); +}); + +$('#gPaypaymentOptionLink').on('click', function (event) { + $('#placeOrderButton').hide(); +}); + +$('#visaCheckoutPaymentOptionLink').on('click', function (event) { + $('#placeOrderButton').hide(); +}); + +$('#creditCardPaymentOptionLink').on('click', function (event) { + $('#placeOrderButton').show(); +}); module.exports = base; diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/account/payment/paymentForm.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/account/payment/paymentForm.isml index b230d50..5a15f16 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/account/payment/paymentForm.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/account/payment/paymentForm.isml @@ -3,7 +3,7 @@ - +
>${month.label} -
- -
+ + ${Resource.msg('payment.card.month.missing', 'forms', null)} + ${Resource.msg('error.message.creditexpiration.expired', 'forms', null)} + @@ -89,7 +90,9 @@ -
+ + ${Resource.msg('payment.card.year.missing', 'forms', null)} + @@ -139,7 +142,7 @@
+ ${pdict.paymentForm.billToAddressFields.lastName.mandatory === true ? 'required' : ''}"> diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutButtons.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutButtons.isml index a532a20..361fc40 100755 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutButtons.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutButtons.isml @@ -13,5 +13,9 @@
+
+ +
+
diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutOptions.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutOptions.isml index 748c7a8..4259185 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutOptions.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/cart/checkoutOptions.isml @@ -7,6 +7,6 @@ - + diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/creditCardForm.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/creditCardForm.isml index c8eb7a7..69aeee2 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/creditCardForm.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/creditCardForm.isml @@ -2,9 +2,10 @@ + - + @@ -53,7 +54,12 @@ -
+ + ${Resource.msg('payment.card.month.missing', 'forms', null)} + + ${Resource.msg('error.message.creditexpiration.expired', 'forms', null)} + +
@@ -77,7 +83,10 @@ -
+ + ${Resource.msg('payment.card.year.missing', 'forms', null)} + +
@@ -108,7 +117,7 @@
- +
diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/applePayTab.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/applePayTab.isml index 3d3e17e..a770c44 100755 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/applePayTab.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/applePayTab.isml @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/googlePayTab.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/googlePayTab.isml index 35146f3..980fccd 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/googlePayTab.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/googlePayTab.isml @@ -3,7 +3,7 @@ -------------------------------------------------------------- diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckout.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckout.isml index 55344b3..6f8ccbd 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckout.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckout.isml @@ -40,7 +40,9 @@ - Visa SRC +
+ Visa SRC +
diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckoutTab.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckoutTab.isml index 882173e..2e85ce3 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckoutTab.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/billing/paymentOptions/visaCheckoutTab.isml @@ -1,5 +1,5 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/checkout.isml b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/checkout.isml index 77bb66b..1256258 100644 --- a/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/checkout.isml +++ b/cartridges/int_cybs_sfra/cartridge/templates/default/checkout/checkout.isml @@ -112,7 +112,7 @@ - diff --git a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/ConversionDetailsApi.js b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/ConversionDetailsApi.js index d5ce510..2b07803 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/ConversionDetailsApi.js +++ b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/ConversionDetailsApi.js @@ -96,7 +96,7 @@ var authNames = []; var contentTypes = ['application/json;charset=utf-8']; - var accepts = ['application/hal+json', 'application/xml']; + var accepts = ['application/hal+json;charset=utf-8']; var returnType = ReportingV3ConversionDetailsGet200Response; return this.apiClient.callApi( diff --git a/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js b/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js index 4e17b30..ec85ffb 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js +++ b/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js @@ -23,7 +23,7 @@ var LogfileMaxSize = '5242880'; // 10 MB In Bytes * Send this value in all requests that are sent through the partner solution. CyberSource assigns the ID to the partner. * Note When you see a partner ID of 999 in reports, the partner ID that was submitted is incorrect. */ -var SolutionId = 'QBZT2URS'; +var SolutionId = '2TLC4V9R'; var CruiseDDCEndPoint = { Stage: 'https://centinelapistag.cardinalcommerce.com/V1/Cruise/Collect', @@ -67,6 +67,7 @@ function getConfig(config) { merchantKeyId: config.merchantKeyId || customPreferences.Core.Preferences.MerchantKeyId.getValue(), merchantsecretKey: config.merchantSecretKey || customPreferences.Core.Preferences.MerchantKeySecret.getValue(), developerId: config.developerId || customPreferences.Core.Preferences.DeveloperId.getValue(), + CommerceIndicator: config.CommerceIndicator || customPreferences.Core.Preferences.CommerceIndicator.getValue(), // Delivery address verification davEnabled: config.davEnabled || customPreferences.DeliveryAddressVerification.Preferences.DAVEnabled.getValue(), @@ -98,13 +99,6 @@ function getConfig(config) { taxShipFromCountryCode: config.taxShipFromCountryCode || customPreferences.TaxConfiguration.Preferences.ShipFromCountryCode.getValue(), calculateTaxOnRoute: [{ route: 'CheckoutShippingServices-SubmitShipping' - }, - { - route: 'CheckoutServices-SubmitPayment' - }, - { - route: 'CheckoutServices-PlaceOrder', - recalculate: true } ], taxCookieId: '_taxvalue', diff --git a/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js b/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js index 753a51c..14ba0a4 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js +++ b/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js @@ -83,6 +83,17 @@ module.exports = { flags: { mandatory: false } + }, + /** @type {CustomPreference} */ + CommerceIndicator: { + id: 'Cybersource_CommerceIndicator' , + display_name: 'Commerce Indicator', + description: '', + type: Types.EnumOfString, + default: 'internet', + flags: { + mandatory: false + } } } }, diff --git a/cartridges/int_cybs_sfra_base/cartridge/controllers/WebhookNotification.js b/cartridges/int_cybs_sfra_base/cartridge/controllers/WebhookNotification.js index d0b2eb0..c4b609f 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/controllers/WebhookNotification.js +++ b/cartridges/int_cybs_sfra_base/cartridge/controllers/WebhookNotification.js @@ -15,140 +15,153 @@ var cybersourceRestApi = require('../apiClient/index'); var instance; -server.use('tokenUpdate', function (req, res, next) { - if (req.httpMethod === 'POST') { - var digitalSignature = req.httpHeaders.get('v-c-signature'); - var payload = JSON.parse(req.body); - var organizationId = payload.payload[0].organizationId; - var message = JSON.stringify(payload.payload[0]); + server.use('tokenUpdate', function (req, res, next) { + if (configObject.networkTokenizationEnabled && req.httpMethod === 'POST') { + var digitalSignature = req.httpHeaders.get('v-c-signature'); + var payload = JSON.parse(req.body); + var organizationId = payload.payload[0].organizationId; + var message = JSON.stringify(payload.payload); - if (validator(digitalSignature, message, organizationId)) { - var webhookId = payload.webhookId; - var data = payload.payload[0].data._links; - var instrumentIdentifierLink = data.instrumentIdentifiers[0].href; - var instrumentIdentifier = instrumentIdentifierLink.substring(instrumentIdentifierLink.lastIndexOf('/') + 1); - var customerLink = data.customers[0].href; - var customerId = customerLink.substring(customerLink.lastIndexOf('/') + 1); - var paymentInstrument; - if (data.paymentInstruments) { - var paymentInstrumentLink = data.paymentInstruments[0].href; - paymentInstrument = paymentInstrumentLink.substring(paymentInstrumentLink.lastIndexOf('/') + 1); - } - - instance = new cybersourceRestApi.InstrumentIdentifierApi(configObject); - var cardData; var cardNumber; var state; - try { // eslint-disable-line no-useless-catch - instance.getInstrumentIdentifier(instrumentIdentifier, configObject.profileId, function (data, error, response) { - if (!error) { - cardData = data.tokenizedCard.card; - cardNumber = data.card.number; - state = data.tokenizedCard.state; - } else { - throw new Error(data); - } - }); - } catch (error) { - throw error; - } + if (validator(digitalSignature, message, organizationId)) { + var webhookId = payload.webhookId; + var data = payload.payload[0].data._links; + var instrumentIdentifierLink = data.instrumentIdentifiers[0].href; + var instrumentIdentifier = instrumentIdentifierLink.substring(instrumentIdentifierLink.lastIndexOf('/') + 1); + var customerLink = data.customers[0].href; + var customerId = customerLink.substring(customerLink.lastIndexOf('/') + 1); + var paymentInstrument; + if (data.paymentInstruments) { + var paymentInstrumentLink = data.paymentInstruments[0].href; + paymentInstrument = paymentInstrumentLink.substring(paymentInstrumentLink.lastIndexOf('/') + 1); + } - if (state == 'ACTIVE') { - instance = new cybersourceRestApi.CustomerApi(configObject); - var email; + instance = new cybersourceRestApi.InstrumentIdentifierApi(configObject); + var cardData; var cardNumber; var state; try { // eslint-disable-line no-useless-catch - instance.getCustomer(customerId, configObject.profileId, function (data, error, response) { + instance.getInstrumentIdentifier(instrumentIdentifier, configObject.profileId, function (data, error, response) { if (!error) { - email = data._embedded.defaultPaymentInstrument.billTo.email; + cardData = data.tokenizedCard.card; + cardNumber = data.card.number; + state = data.tokenizedCard.state; } else { - throw new Error(data); + Logger.info('Card details does not exist'); + res.setStatusCode(404); } }); } catch (error) { - throw error; + Logger.info('Token update failed'+ error); + res.setStatusCode(404); } - var customer = CustomerMgr.getCustomerByLogin(email); - var wallet = customer.profile.wallet; - var paymentInstruments = wallet.getPaymentInstruments(); - var paymentToDelete = array.find(paymentInstruments, function (item) { - var token = item.creditCardToken; - var tokenInfo = mapper.deserializeTokenInformation(token); - return instrumentIdentifier === tokenInfo.instrumentIdentifier.id; - }); - - Transaction.wrap(function () { - var cardHolder = paymentToDelete.creditCardHolder; - var cardType = paymentToDelete.creditCardType; - var cardToken = paymentToDelete.creditCardToken; - wallet.removePaymentInstrument(paymentToDelete); - var newPaymentInstrument = wallet.createPaymentInstrument(PaymentInstrument.METHOD_CREDIT_CARD); - newPaymentInstrument.setCreditCardHolder(cardHolder); - var newCardNumber = cardNumber.slice(0, -4) + cardData.suffix; - newPaymentInstrument.setCreditCardNumber(newCardNumber); - newPaymentInstrument.setCreditCardType(cardType); - newPaymentInstrument.setCreditCardExpirationMonth(Number(cardData.expirationMonth)); - newPaymentInstrument.setCreditCardExpirationYear(Number(cardData.expirationYear)); - if (empty(paymentInstrument)) { - var oldToken = mapper.deserializeTokenInformation(cardToken); - paymentInstrument = oldToken.paymentInstrument.id; + if (state == 'ACTIVE') { + instance = new cybersourceRestApi.CustomerApi(configObject); + var email; + try { // eslint-disable-line no-useless-catch + instance.getCustomer(customerId, configObject.profileId, function (data, error, response) { + if (!error) { + email = data._embedded.defaultPaymentInstrument.billTo.email; + } else { + Logger.info('Customer data does not exist'); + res.setStatusCode(404); + } + }); + } catch (error) { + Logger.info('Token update failed'+ error); + res.setStatusCode(404); } - var tokenInfo = { - instrumentIdentifier: { id: instrumentIdentifier }, - paymentInstrument: { id: paymentInstrument } - }; - var token = mapper.serializeTokenInformation(tokenInfo); - newPaymentInstrument.setCreditCardToken(token); - }); - } else { - Logger.info('Network token state is not Active'); + var customer = CustomerMgr.getCustomerByLogin(email); + var wallet = customer.profile.wallet; + var paymentInstruments = wallet.getPaymentInstruments(); + var paymentToDelete = array.find(paymentInstruments, function (item) { + var token = item.creditCardToken; + var tokenInfo = mapper.deserializeTokenInformation(token); + return instrumentIdentifier === tokenInfo.instrumentIdentifier.id; + }); + + Transaction.wrap(function () { + var cardHolder = paymentToDelete.creditCardHolder; + var cardType = paymentToDelete.creditCardType; + var cardToken = paymentToDelete.creditCardToken; + wallet.removePaymentInstrument(paymentToDelete); + var newPaymentInstrument = wallet.createPaymentInstrument(PaymentInstrument.METHOD_CREDIT_CARD); + newPaymentInstrument.setCreditCardHolder(cardHolder); + var newCardNumber = cardNumber.slice(0, -4) + cardData.suffix; + newPaymentInstrument.setCreditCardNumber(newCardNumber); + newPaymentInstrument.setCreditCardType(cardType); + newPaymentInstrument.setCreditCardExpirationMonth(Number(cardData.expirationMonth)); + newPaymentInstrument.setCreditCardExpirationYear(Number(cardData.expirationYear)); + if (empty(paymentInstrument)) { + var oldToken = mapper.deserializeTokenInformation(cardToken); + paymentInstrument = oldToken.paymentInstrument.id; + } + var tokenInfo = { + instrumentIdentifier: { id: instrumentIdentifier }, + paymentInstrument: { id: paymentInstrument } + }; + + var token = mapper.serializeTokenInformation(tokenInfo); + newPaymentInstrument.setCreditCardToken(token); + res.setStatusCode(200); + }); + } else { + Logger.info('Network token state is not Active'); + res.setStatusCode(404); + } + } + else{ + res.setStatusCode(404); } } - } -}); + else{ + Logger.info('Network token updates disabled'); + res.setStatusCode(404); + } + }); -function validator(digitalSignature, message, merchantId) { - var signatureParts; - var timestamp; - var keyId; - try { - signatureParts = digitalSignature.split(';'); - timestamp = parseInt(signatureParts[0].split('=')[1]); - keyId = signatureParts[1].split('=')[1]; - signature = signatureParts[2].split('=')[1]; - } catch (e) { - Logger.error('Invalid digital signature format'); - } + function validator(digitalSignature, message, merchantId) { + var signatureParts; + var timestamp; + var keyId; + try { + signatureParts = digitalSignature.split(';'); + timestamp = parseInt(signatureParts[0].split('=')[1]); + keyId = signatureParts[1].split('=')[1]; + signature = signatureParts[2].split('=')[1]; + } catch (e) { + Logger.error('Invalid digital signature format'); + } - if (isValidTimestamp(timestamp)) { - const regeneratedSignature = regenerateSignature(timestamp, message, merchantId); - if (regeneratedSignature.toString() === Encoding.fromBase64(signature).toString()) { - return true; + if (isValidTimestamp(timestamp)) { + const regeneratedSignature = regenerateSignature(timestamp, message, merchantId); + if (regeneratedSignature.toString() === Encoding.fromBase64(signature).toString()) { + return true; + } + Logger.error('No match in signature'); + return false; } - Logger.error('No match in signature'); - return false; } -} -function regenerateSignature(timestamp, message, merchantId) { - const timestampedMessage = `${timestamp}.${message}`; - const key = getSecurityKey(merchantId); - try { - var hmac = new Mac('HmacSHA256'); - return hmac.digest(new Bytes(timestampedMessage, 'utf8'), Encoding.fromBase64(key)); - } catch (e) { - throw new Error('Failed to calculate hmac-sha256'); + function regenerateSignature(timestamp, message, merchantId) { + const timestampedMessage = `${timestamp}.${message}`; + const key = getSecurityKey(merchantId); + try { + var hmac = new Mac('HmacSHA256'); + return hmac.digest(new Bytes(timestampedMessage, 'utf8'), Encoding.fromBase64(key)); + } catch (e) { + throw new Error('Failed to calculate hmac-sha256'); + } } -} -function getSecurityKey(merchantId) { - var obj = CustomObjectMgr.getCustomObject('Network Tokens Webhook', merchantId); - return obj.custom.SecurityKey; -} + function getSecurityKey(merchantId) { + var obj = CustomObjectMgr.getCustomObject('Network Tokens Webhook', merchantId); + return obj.custom.SecurityKey; + } -function isValidTimestamp(timestamp) { - const tolerance = 60 * 60 * 1000; - const currentTime = Date.now(); - return currentTime - timestamp < tolerance; -} + function isValidTimestamp(timestamp) { + const tolerance = 60 * 60 * 1000; + const currentTime = Date.now(); + return currentTime - timestamp < tolerance; + } module.exports = server.exports(); diff --git a/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/applePay.js b/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/applePay.js index 3d5c1c8..e00fa78 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/applePay.js +++ b/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/applePay.js @@ -61,6 +61,17 @@ exports.authorizeOrderPayment = function (order, response) { email: response.payment.shippingContact.emailAddress, phoneNumber: response.payment.shippingContact.phoneNumber }, + shipTo: { + firstName: response.payment.shippingContact.givenName, + lastName: response.payment.shippingContact.familyName, + address1: response.payment.shippingContact.addressLines[0], + locality: response.payment.shippingContact.locality, + administrativeArea: response.payment.shippingContact.administrativeArea, + postalCode: response.payment.shippingContact.postalCode, + country: response.payment.shippingContact.countryCode, + email: response.payment.shippingContact.emailAddress, + phoneNumber: response.payment.shippingContact.phoneNumber + }, lineItems: mapper.MapOrderLineItems(order.allLineItems, true) } }; diff --git a/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit.js b/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit.js index 3de4cb9..dbe8ad7 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit.js +++ b/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit.js @@ -281,7 +281,7 @@ function Authorize(orderNumber, paymentInstrument, paymentProcessor) { paymentInstrument.paymentTransaction.setPaymentProcessor(paymentProcessor); paymentInstrument.paymentTransaction.custom.requestId = result.id; paymentInstrument.paymentTransaction.custom.reconciliationId = result.reconciliationId; - if (card.gPayToken === null) { + if (!empty(card.gPayToken)) { paymentInstrument.paymentTransaction.custom.paymentDetails = paymentInstrument.creditCardHolder + ', ' + paymentInstrument.maskedCreditCardNumber + ', ' + paymentInstrument.creditCardType + ', ' + paymentInstrument.creditCardExpirationMonth + '/' + paymentInstrument.creditCardExpirationYear; } else { diff --git a/cartridges/int_cybs_sfra_base/cartridge/scripts/http/payments.js b/cartridges/int_cybs_sfra_base/cartridge/scripts/http/payments.js index b38a956..500ffd9 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/scripts/http/payments.js +++ b/cartridges/int_cybs_sfra_base/cartridge/scripts/http/payments.js @@ -37,7 +37,7 @@ function httpAuthorizeWithToken(cardData, customerEmail, referenceInformationCod deviceSessionId.ipAddress = session.privacy.ipAddress; var processingInformation = new cybersourceRestApi.Ptsv2paymentsProcessingInformation(); - processingInformation.commerceIndicator = 'internet'; + processingInformation.commerceIndicator = configObject.CommerceIndicator.value; processingInformation.actionList = []; if (!configObject.fmeDmEnabled) { processingInformation.actionList.push('DECISION_SKIP'); @@ -194,7 +194,7 @@ function httpZeroDollarAuth( } var processingInformation = new cybersourceRestApi.Ptsv2paymentsProcessingInformation(); - processingInformation.commerceIndicator = 'internet'; + processingInformation.commerceIndicator = configObject.CommerceIndicator.value ; processingInformation.actionList = []; if (session.getCustomer().getProfile().custom.customerID != null) { processingInformation.actionTokenTypes = [ @@ -310,7 +310,7 @@ function httpZeroDollarAuthWithTransientToken( var processingInformation = new cybersourceRestApi.Ptsv2paymentsProcessingInformation(); - processingInformation.commerceIndicator = 'internet'; + processingInformation.commerceIndicator = configObject.CommerceIndicator.value; processingInformation.actionList = []; if (session.getCustomer().getProfile().custom.customerID != null) { @@ -412,7 +412,7 @@ function httpAuthorizeWithTransientToken(transientToken, customerEmail, referenc deviceSessionId.fingerprintSessionId = session.privacy.dfID; var processingInformation = new cybersourceRestApi.Ptsv2paymentsProcessingInformation(); - processingInformation.commerceIndicator = 'internet'; + processingInformation.commerceIndicator = configObject.CommerceIndicator.value; processingInformation.actionList = []; if (!configObject.fmeDmEnabled) { processingInformation.actionList.push('DECISION_SKIP'); @@ -577,7 +577,7 @@ function httpAuthorizeWithVisaSrc(encPaymentData, callID, customerEmail, referen deviceSessionId.fingerprintSessionId = session.privacy.dfID; var processingInformation = new cybersourceRestApi.Ptsv2paymentsProcessingInformation(); - processingInformation.commerceIndicator = 'internet'; + processingInformation.commerceIndicator = configObject.CommerceIndicator.value; processingInformation.visaCheckoutId = callID; processingInformation.paymentSolution = 'visacheckout'; processingInformation.actionList = []; diff --git a/documentation/Cybersource REST Cartridge for Salesforce B2C Commerce.pdf b/documentation/Cybersource REST Cartridge for Salesforce B2C Commerce.pdf new file mode 100644 index 0000000..4aab7c4 Binary files /dev/null and b/documentation/Cybersource REST Cartridge for Salesforce B2C Commerce.pdf differ diff --git a/documentation/Storefront Reference Architecture REST Cartridge.pdf b/documentation/Storefront Reference Architecture REST Cartridge.pdf deleted file mode 100644 index 392715a..0000000 Binary files a/documentation/Storefront Reference Architecture REST Cartridge.pdf and /dev/null differ diff --git a/documentation/markdown/Configure-cartridge.md b/documentation/markdown/Configure-cartridge.md index e33b532..b99794c 100644 --- a/documentation/markdown/Configure-cartridge.md +++ b/documentation/markdown/Configure-cartridge.md @@ -13,7 +13,7 @@ To set up the cartridge path: 2. For the Cartridges, enter **int_cybs_sfra:int_cybs_sfra_base:app_storefront_base** and select **Apply** ### Step 2: Upload metadata -Cybersource cartridge installed from Market place comes with metadata to import. +The Cybersource Cartridge contains metadata that needs to be imported. 1. Go to **Cybersource/metadata/payments_metadata/sites/** folder. 2. Rename **yourSiteID** folder name with your site ID in Business Manager (this can be found by looking up **Administration->Sites->Manage Sites**) 3. Zip **payments_metadata** folder. @@ -42,6 +42,7 @@ Cybersource REST KeyId | Cybersource REST Key ID Cybersource REST Secret Key | Cybersource REST Secret Key Developer ID | Unique identifier generated by Cybersource for System Integrator Credit/Debit Card Transaction Type | Select Sale/Auth Transaction Type +CommerceIndicator | Select MOTO/Internet #### Services (Required) diff --git a/documentation/markdown/Configure-features.md b/documentation/markdown/Configure-features.md index 965fd26..2df9a81 100644 --- a/documentation/markdown/Configure-features.md +++ b/documentation/markdown/Configure-features.md @@ -136,7 +136,8 @@ TTL (Time To Live) | Time, in milliseconds between generating a new fingerprint ### **6. Capture Service** -A single function is available to make capture requests. +A single function is available to make capture requests. Please note that these functions are not available +to use in the Salesforce B2C Commerce UI without customisation. httpCapturePayment(requestID, merchantRefCode, purchaseTotal, currency) diff --git a/documentation/markdown/Configure-payment-method.md b/documentation/markdown/Configure-payment-method.md index 318fd11..bf9f21d 100644 --- a/documentation/markdown/Configure-payment-method.md +++ b/documentation/markdown/Configure-payment-method.md @@ -14,10 +14,10 @@ In the Business Manager, go to **Merchant Tools > Ordering > Payment Methods** and select **CREDIT_CARD**. And in **CREDIT_CARD details**, double check if **Payment Processor** = **"PAYMENTS_CREDIT"** Cybersource Cartridge supports the following ways of processing Credit Card - a. Flex Microform 0.11 + a. Microform 0.11 [link](https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex-api/developer/ctv/rest/flex-api/microform-integ-v011.html) b. Direct Cybersource Payment API - #### a. To Setup Flex Microform 0.11 + #### a. To Setup Microform 0.11 Step 1: Upload Cybersource metadata in Business Manager. If not follow "Step 2: Upload metadata" or import **"metadata/payment_metadata/meta/FlexMicroform.xml"** in Business Manager (**Administration > Site Development > Import & Export**) @@ -42,7 +42,8 @@ Step 1: Upload Cybersource metadata in Business Manager. If not follow "Step 2: #### Payer Authentication (3D Secure) **Prerequisite** - Please contact your Cybersource Representative to sign up and receive your Payer Authentication credentials. + If you wish to process card payments with Payer Authentication, please ensure your Cybersource account + has been enabled for it. Please contact your Cybersource representative if you are unsure. Step 1: Upload Cybersource metadata in Business Manager. If not follow "Step 2: Upload metadata" or import **"metadata/payment_metadata/meta/PayerAuthentication.xml"** in Business Manager (**Administration > Site Development > Import & Export**) @@ -52,15 +53,12 @@ Step 1: Upload Cybersource metadata in Business Manager. If not follow "Step 2: Field | Description ------------ | ------------- Enable Payer Authentication | Enable or Disable Payer Authentication service - Cruise Org Unit Id | GUID to identify the merchant organization within Payer Authentication systems - Cruise API Key | A shared secret value between the merchant and Payer Authentication systems. This value should never be exposed to the public - Cruise API Identifier | GUID used to identify the specific API Key - Cruise End Point | Environment details of Cruise API #### Enforce Strong Consumer Authentication When Payer Authentication is enabled, if a transaction gets declined with the reason as Strong Customer Authentication required, then another request will be sent from cartridge automatically for the same order and the customer will be 3DS challenged. -In case merchants would like the cardholder to be 3DS Challenged when saving a card IsSCAEnabled setting can be updated to enable it for credit cards. +In case merchants would like the cardholder to be 3DS Challenged when saving a card, IsSCAEnabled +setting can be updated to enable it for credit cards. Note: The scaEnabled setting is applicable only if Payer Authentication is enabled. diff --git a/documentation/markdown/Install-catridge-WrkSpace-Setup.md b/documentation/markdown/Install-catridge-WrkSpace-Setup.md index 3222ae0..36e19af 100644 --- a/documentation/markdown/Install-catridge-WrkSpace-Setup.md +++ b/documentation/markdown/Install-catridge-WrkSpace-Setup.md @@ -2,7 +2,8 @@ ## Install the Cartridge and Setup Workspace ### Step 1: Install the Cartridge ### -Cybersource's Storefront Reference Architecture Cartridge can be installed from Salesforce Commerce Cloud's marketplace [link](https://www.salesforce.com/products/commerce-cloud/partner-marketplace/partners/cybersource/) +Cybersource's REST Cartridge for Salesforce B2C Commerce can be downloaded from the Salesforce +AppExchange [link](https://www.salesforce.com/products/commerce-cloud/partner-marketplace/partners/cybersource/) ### Step 2: Setup workspace 1. Create a folder “Cybersource” in your workspace and place the cartridge (int_cybs_sfra and int_cybs_sfra_base) downloaded from Marketplace. diff --git a/documentation/markdown/Release-notes.md b/documentation/markdown/Release-notes.md index 8719036..359fa97 100644 --- a/documentation/markdown/Release-notes.md +++ b/documentation/markdown/Release-notes.md @@ -1,5 +1,9 @@ ## Release Notes +**Version 24.3.0 (August 2024)** +• Upgraded the cartridge to support SFRA v7.0. +• Added Commerce Indicator MOTO. + **Version 24.2.1 (May 2024)** • Checkmarx issues fixed. • Device fingerprint issue fixed. diff --git a/metadata/payments_metadata/meta/Core.xml b/metadata/payments_metadata/meta/Core.xml index 409448a..c088db0 100644 --- a/metadata/payments_metadata/meta/Core.xml +++ b/metadata/payments_metadata/meta/Core.xml @@ -49,6 +49,22 @@ + + Commerce Indicator + enum-of-string + false + false + + + internet + internet + + + moto + MOTO + + + @@ -59,6 +75,7 @@ + diff --git a/metadata/payments_metadata/meta/merged.xml b/metadata/payments_metadata/meta/merged.xml index 9b5903c..5e23d11 100644 --- a/metadata/payments_metadata/meta/merged.xml +++ b/metadata/payments_metadata/meta/merged.xml @@ -49,6 +49,22 @@ + + Commerce Indicator + enum-of-string + false + false + + + internet + internet + + + moto + MOTO + + + Enable Delivery Address Verification Services Enable or Disable Delivery Address Verification for Cybersource Cartridge @@ -370,6 +386,7 @@ + Delivery Address Verification Configuration diff --git a/package.json b/package.json index 48df243..0821dff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "int_cybs_sfra", - "version": "21.1.0", + "version": "20.1.0", "description": "New overlay cartridge", "main": "index.js", "scripts": { @@ -36,38 +36,46 @@ "release": "node bin/Makefile release --" }, "dependencies": { + "autoprefixer": "^10.4.14", + "bootstrap": "^4.6.2", "chai-subset": "^1.6.0", - "cleave.js": "~1.4.2", - "copy-webpack-plugin": "^5.1.1", - "dompurify": "^3.1.0", + "cleave.js": "^1.5.3", + "copy-webpack-plugin": "^11.0.0", + "dompurify": "^3.1.6", "jquery": "^3.5.1", - "request-promise": "^4.2.5", - "webdriver": "^5.15.6" + "link": "^2.1.0", + "mini-css-extract-plugin": "^2.7.6", + "request-promise": "^4.2.6", + "webdriver": "^7.33.0" }, "devDependencies": { "chai": "^3.5.0", - "css-loader": "^0.28.11", - "eslint": "^7.7.0", - "eslint-config-airbnb-base": "^14.2.0", - "eslint-config-prettier": "^6.11.0", + "css-loader": "^6.0.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "eslint": "^8.56.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.1.0", "eslint-formatter-pretty": "^1.3.0", "eslint-formatter-summary": "^1.0.2", - "eslint-plugin-import": "^2.22.0", + "eslint-plugin-import": "^2.29.0", "eslint-plugin-prettier": "^3.1.3", - "istanbul": "^0.4.4", - "mocha": "^5.2.0", - "node-sass": "^4.9.0", - "postcss-loader": "^2.1.5", + "mocha": "^10.0.0", + "node-sass": "^9.0.0", + "nyc": "^15.1.0", + "postcss-loader": "^7.0.0", "proxyquire": "1.7.4", - "sass-loader": "^7.0.3", - "sgmf-scripts": "^2.0.0", - "sinon": "^1.17.4", - "stylelint": "^13.5.0", - "stylelint-config-standard": "^12.0.0", - "stylelint-scss": "^3.17.2", + "sass": "^1.69.7", + "sass-loader": "^13.3.2", + "sgmf-scripts": "^3.0.0", + "sinon": "^17.0.1", + "stylelint": "^15.4.0", + "stylelint-config-standard": "^33.0.0", + "stylelint-scss": "^4.7.0", + "webpack": "^5.0.0", + "webpack-remove-empty-scripts": "^1.0.4", "xml-formatter": "^2.0.1" }, "paths": { "base": "../storefront-reference-architecture/cartridges/app_storefront_base/" } -} +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index cc00bd2..edd8e84 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,9 +1,13 @@ 'use strict'; -var path = require('path'); -var ExtractTextPlugin = require('sgmf-scripts')['extract-text-webpack-plugin']; -var sgmfScripts = require('sgmf-scripts'); +const path = require('path'); + +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); +var CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); +const sgmfScripts = require('sgmf-scripts'); + module.exports = [{ mode: 'production', @@ -14,59 +18,83 @@ module.exports = [{ filename: '[name].js' }, plugins: [ - new CopyPlugin([{ - from: path.resolve('./cartridges/int_cybs_sfra/cartridge/client/default/custom'), - to: path.resolve('./cartridges/int_cybs_sfra/cartridge/static/default/custom') - } - ]) + new CopyPlugin({ + patterns: [ + { + from: path.resolve('./cartridges/int_cybs_sfra/cartridge/client/default/custom'), + to: path.resolve('./cartridges/int_cybs_sfra/cartridge/static/default/custom') + }, + ], + }) ] -}, { +}, +{ mode: 'none', name: 'scss', entry: sgmfScripts.createScssPath(), output: { path: path.resolve('./cartridges/int_cybs_sfra/cartridge/static'), - filename: '[name].css' }, module: { - rules: [{ - test: /\.scss$/, - use: ExtractTextPlugin.extract({ - use: [{ - loader: 'css-loader', - options: { - url: false, - minimize: true - } - }, { - loader: 'postcss-loader', - options: { - plugins: [ - require('autoprefixer')() - ] + rules: [ + { + test: /\.scss$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + esModule: false + } + }, + { + loader: 'css-loader', + options: { + url: false + } + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [require('autoprefixer')()] + } + } + }, + { + loader: 'sass-loader', + options: { + implementation: require('sass'), + sassOptions: { + includePaths: [ + path.resolve( + process.cwd(), + '../storefront-reference-architecture/node_modules/' + ), + path.resolve( + process.cwd(), // eslint-disable-next-line max-len + '../storefront-reference-architecture/node_modules/flag-icon-css/sass' + )] + } + } } - }, { - loader: 'sass-loader', - options: { - includePaths: [ - path.resolve( - process.cwd(), - '../storefront-reference-architecture/node_modules/' - ), - path.resolve( - process.cwd(), // eslint-disable-next-line max-len - '../storefront-reference-architecture/node_modules/flag-icon-css/sass' - )] - } - }] - }) - }] + ] + } + ] }, + plugins: [ - new ExtractTextPlugin({ filename: '[name].css' }), - new CopyPlugin([{ - from: path.resolve('./cartridges/int_cybs_sfra/cartridge/client/default/images'), - to: path.resolve('./cartridges/int_cybs_sfra/cartridge/static/default/images') - }]) - ] -}]; + new RemoveEmptyScriptsPlugin(), + new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].css' }), + new CopyPlugin({ + patterns: [ + { + from: path.resolve('./cartridges/int_cybs_sfra/cartridge/client/default/images'), + to: path.resolve('./cartridges/int_cybs_sfra/cartridge/static/default/images') + }, + ], + }) + ], + optimization: { + minimizer: ['...', new CssMinimizerPlugin()] + } +}]; \ No newline at end of file