diff --git a/cartridges/int_bolt_embedded_sfra/cartridge/scripts/hooks/payment/processor/bolt_pay.js b/cartridges/int_bolt_embedded_sfra/cartridge/scripts/hooks/payment/processor/bolt_pay.js index c05b273..ca57f21 100644 --- a/cartridges/int_bolt_embedded_sfra/cartridge/scripts/hooks/payment/processor/bolt_pay.js +++ b/cartridges/int_bolt_embedded_sfra/cartridge/scripts/hooks/payment/processor/bolt_pay.js @@ -4,8 +4,6 @@ var Resource = require('dw/web/Resource'); var Transaction = require('dw/system/Transaction'); var OrderMgr = require('dw/order/OrderMgr'); -var StringUtils = require('dw/util/StringUtils'); -var Site = require('dw/system/Site'); var HttpResult = require('dw/svc/Result'); // Script includes @@ -15,6 +13,7 @@ var oAuth = require('~/cartridge/scripts/services/oAuth'); var constants = require('~/cartridge/scripts/util/constants'); var boltAccountUtils = require('~/cartridge/scripts/util/boltAccountUtils'); var boltPaymentUtils = require('~/cartridge/scripts/util/boltPaymentUtils'); +var boltPayAuthRequestBuilder = require('~/cartridge/scripts/util/boltPayAuthRequestBuilder'); var logUtils = require('~/cartridge/scripts/util/boltLogUtils'); var log = logUtils.getLogger('Auth'); @@ -108,21 +107,12 @@ function authorize(orderNumber, paymentInstrument, paymentProcessor) { paymentInstrument.paymentTransaction.setPaymentProcessor(paymentProcessor); }); var order = OrderMgr.getOrder(orderNumber); - // save card to bolt account - // if save card is success, use the new credit card id for authorization - if (boltAccountUtils.loginAsBoltUser() && !empty(paymentInstrument.getCreditCardToken())) { - var saveCardResult = boltAccountUtils.saveCardToBolt(order, paymentInstrument); - if (saveCardResult.success) { - Transaction.wrap(function () { - paymentInstrument.custom.boltPaymentMethodId = saveCardResult.newPaymentMethodID; - }); - } - } // build auth request - var authRequestObj = getAuthRequest(order, paymentInstrument); + var authRequestObj = boltPayAuthRequestBuilder.build(order, paymentInstrument); if (authRequestObj.error) { log.error(authRequestObj.errorMsg); + return { error: true, errorCode: '000000', errorMessage: authRequestObj.errorMsg }; } // only attach oauth token if it is available and the user has not logged out @@ -132,7 +122,7 @@ function authorize(orderNumber, paymentInstrument, paymentProcessor) { bearerToken = 'Bearer '.concat(boltOAuthToken); } - // send auth call + // Send auth call, note: saves both new address and new payment method. var response = boltHttpUtils.restAPIClient( constants.HTTP_METHOD_POST, constants.AUTH_CARD_URL, @@ -157,125 +147,9 @@ function authorize(orderNumber, paymentInstrument, paymentProcessor) { paymentInstrument.getPaymentTransaction().setTransactionID(orderNumber); }); - // save shipping address to bolt account - if (boltAccountUtils.loginAsBoltUser()) { - boltAccountUtils.saveAddressToBolt(order); - } - return { error: false }; } -/** - * Create Authorization Request Body - * @param {dw.order.Order} order - SFCC order object - * @param {dw.order.PaymentInstrument} paymentInstrument - payment instrument to authorize - * @return {Object} returns an response object - */ -function getAuthRequest(order, paymentInstrument) { - if (empty(paymentInstrument)) { - return { error: true, errorMsg: 'Missing payment instrument.' }; - } - - if (empty(order.billingAddress)) { - return { error: true, errorMsg: 'SFCC basket has not billing address.' }; - } - - var billingAddress = order.getBillingAddress(); - var userIdentifier = { - email: order.getCustomerEmail(), - phone: billingAddress.getPhone() - }; - var userIdentity = { - first_name: billingAddress.getFirstName(), - last_name: billingAddress.getLastName() - }; - - var boltBillingAddress = { - street_address1: billingAddress.getAddress1() || '', - street_address2: billingAddress.getAddress2() || '', - locality: billingAddress.getCity() || '', - region: billingAddress.getStateCode() || '', - postal_code: billingAddress.getPostalCode() || '', - country_code: billingAddress.getCountryCode() ? billingAddress.getCountryCode().getValue().toUpperCase() : '', - country: billingAddress.getCountryCode() ? billingAddress.getCountryCode().getDisplayValue() : '', - name: billingAddress.getFullName(), - first_name: billingAddress.getFirstName(), - last_name: billingAddress.getLastName(), - phone_number: billingAddress.getPhone(), - email: order.getCustomerEmail(), - phone: billingAddress.getPhone() || '' - }; - - var request = { - cart: { - order_reference: order.getOrderNo(), - billing_address: boltBillingAddress, - currency: order.currencyCode, - metadata: { - SFCCSessionID: getDwsidCookie() - } - }, - division_id: - Site.getCurrent().getCustomPreferenceValue('boltMerchantDivisionID') || '', - source: constants.DIRECT_PAYMENTS, - user_identifier: userIdentifier, - user_identity: userIdentity, - create_bolt_account: paymentInstrument.custom.boltCreateAccount - }; - - // populate auto capture field if needed - var autoCapture = Site.getCurrent().getCustomPreferenceValue('boltEnableAutoCapture') === true; - if (autoCapture) { - request.auto_capture = true; - } - - // use Bolt payment ID for Bolt - if (boltAccountUtils.loginAsBoltUser() && paymentInstrument.custom.boltPaymentMethodId) { - request.credit_card_id = paymentInstrument.custom.boltPaymentMethodId; - } else { - request.credit_card = { - token: paymentInstrument.getCreditCardToken(), - last4: paymentInstrument.getCreditCardNumberLastDigits(), - bin: paymentInstrument.custom.boltCardBin, - billing_address: boltBillingAddress, - number: '', - expiration: - StringUtils.formatNumber( - paymentInstrument.getCreditCardExpirationYear(), - '0000' - ) - + '-' - + StringUtils.formatNumber( - paymentInstrument.getCreditCardExpirationMonth(), - '00' - ), - postal_code: billingAddress.getPostalCode(), - token_type: constants.BOLT_TOKEN_TYPE - }; - } - - return { - authRequest: request, - error: false - }; -} - -/** - * getDwsidCookie returns DW Session ID from cookie - * @return {string} DW Session ID - */ -function getDwsidCookie() { - var cookies = request.getHttpCookies(); - - for (var i = 0; i < cookies.cookieCount; i++) { // eslint-disable-line no-plusplus - if (cookies[i].name === 'dwsid') { - return cookies[i].value; - } - } - - return ''; -} - /** * sessionLogoutCookieSet returns true if the bolt_sfcc_session_logout is set * @return {boolean} true if the cookie is set otherwise false diff --git a/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltAccountUtils.js b/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltAccountUtils.js index 88f7ae8..4c146fe 100644 --- a/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltAccountUtils.js +++ b/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltAccountUtils.js @@ -178,57 +178,6 @@ exports.saveCardToBolt = function (order, paymentInstrument) { } }; -/** - * Save new address to Bolt or update existing Bolt address - * @param {dw.order.Order} order - SFCC order object - * @returns {void} - no return data - */ -exports.saveAddressToBolt = function (order) { - try { - var shippingAddress = order.getDefaultShipment().getShippingAddress(); - var errorMsg; - // add bolt address id to endpoint if shopper is updating existing address - var addressUrl = !empty(shippingAddress.custom.boltAddressId) ? (constants.SHOPPER_ADDRESS_URL + '/' + shippingAddress.custom.boltAddressId) : constants.SHOPPER_ADDRESS_URL; - var isGift = order.getDefaultShipment().isGift(); - - var request = { - street_address1: shippingAddress.address1 || '', - street_address2: shippingAddress.address2 || '', - locality: shippingAddress.city || '', - region: shippingAddress.stateCode || '', - postal_code: shippingAddress.postalCode || '', - country_code: shippingAddress.countryCode.value || '', - first_name: shippingAddress.firstName || '', - last_name: shippingAddress.lastName || '', - phone: shippingAddress.phone || '', - default: !isGift - }; - - var boltOAuthToken = oAuth.getOAuthToken(); - if (empty(boltOAuthToken)) { - errorMsg = 'Bolt OAuth Token is missing'; - log.error(errorMsg); - return; - } - var bearerToken = 'Bearer '.concat(boltOAuthToken); - - // send save address request to Bolt - var response = boltHttpUtils.restAPIClient( - constants.HTTP_METHOD_POST, - addressUrl, - JSON.stringify(request), - constants.CONTENT_TYPE_JSON, - bearerToken - ); - errorMsg = Resource.msg('error.save.address', 'bolt', null); - if (response.status && response.status === HttpResult.ERROR) { - log.error(errorMsg + (!empty(response.errors) && !empty(response.errors[0].message) ? response.errors[0].message : '')); - } - log.info('address successfully saved to bolt'); - } catch (e) { - log.error(e.message); - } -}; /** * Get bolt payment data which is stored in SFCC basket * @param {dw.order.Basket} basket - the SFCC basket diff --git a/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltPayAuthRequestBuilder.js b/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltPayAuthRequestBuilder.js new file mode 100644 index 0000000..540a541 --- /dev/null +++ b/cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltPayAuthRequestBuilder.js @@ -0,0 +1,412 @@ +'use strict'; + +// API Includes +var Site = require('dw/system/Site'); +var StringUtils = require('dw/util/StringUtils'); + +// Script includes +var LogUtils = require('~/cartridge/scripts/util/boltLogUtils'); +var boltAccountUtils = require('~/cartridge/scripts/util/boltAccountUtils'); +var log = LogUtils.getLogger('BoltAuthRequestBuilder'); +var collections = require('*/cartridge/scripts/util/collections'); +var constants = require('~/cartridge/scripts/util/constants'); + +/** + * Build Auth request body + * @param {dw.order.Order} order SFCC Order + * @param {dw.order.PaymentInstrument} paymentInstrument SFCC Payment Instrument + * @returns {Object | null} Auth request body object + */ +exports.build = function (order, paymentInstrument) { + try { + if (empty(paymentInstrument)) { + return { error: true, errorMsg: 'Missing payment instrument.' }; + } + + if (empty(order.getBillingAddress())) { + return { error: true, errorMsg: 'SFCC basket has not billing address.' }; + } + var billingAddress = order.getBillingAddress(); + var userIdentifier = { + email: order.getCustomerEmail(), + phone: billingAddress.getPhone() + }; + var userIdentity = { + first_name: billingAddress.getFirstName(), + last_name: billingAddress.getLastName() + }; + + var request = { + cart: buildCartField(order, paymentInstrument), + division_id: + Site.getCurrent().getCustomPreferenceValue('boltMerchantDivisionID') || '', + source: constants.DIRECT_PAYMENTS, + user_identifier: userIdentifier, + user_identity: userIdentity, + create_bolt_account: paymentInstrument.custom.boltCreateAccount + }; + + // populate auto capture field if needed + var autoCapture = Site.getCurrent().getCustomPreferenceValue('boltEnableAutoCapture') === true; + if (autoCapture) { + request.auto_capture = true; + } + + // use saved Bolt payment for Auth + if (boltAccountUtils.loginAsBoltUser() && paymentInstrument.custom.boltPaymentMethodId) { + request.credit_card_id = paymentInstrument.custom.boltPaymentMethodId; + } else { // use new credit card for Auth + request.credit_card = buildCreditCardField(order, paymentInstrument); + } + return { + authRequest: request, + error: false + }; + } catch (e) { + var errorMessage = e.message + e.stack; + log.error('Error occurred in auth request build function: ' + errorMessage); + return { + errorMsg: errorMessage, + error: true + }; + } +}; + +/** + * Build credit card field + * @param {dw.order.Order} order SFCC Order + * @param {dw.order.PaymentInstrument} paymentInstrument SFCC Payment Instrument + * @returns {Object | null} Credit card field object + */ +function buildCreditCardField(order, paymentInstrument) { + var billingAddress = order.getBillingAddress(); + return { + token: paymentInstrument.getCreditCardToken(), + last4: paymentInstrument.getCreditCardNumberLastDigits(), + bin: paymentInstrument.custom.boltCardBin, + billing_address: buildBillingAddressField(order), + number: '', + expiration: + StringUtils.formatNumber( + paymentInstrument.getCreditCardExpirationYear(), + '0000' + ) + + '-' + + StringUtils.formatNumber( + paymentInstrument.getCreditCardExpirationMonth(), + '00' + ), + postal_code: billingAddress.getPostalCode(), + token_type: constants.BOLT_TOKEN_TYPE + }; +} + +/** + * Build cart details field + * @param {dw.order.Order} order SFCC Order + * @param {dw.order.PaymentInstrument} paymentInstrument SFCC Payment Instrument + * @returns {Object | null} Bolt cart object or null if there is any error + */ +function buildCartField(order, paymentInstrument) { + var taxAmount = Math.round(order.getTotalTax().getValue() * 100); + var paymentTransaction = paymentInstrument.getPaymentTransaction(); + var amountInCent = Math.round(paymentTransaction.getAmount().getValue() * 100); + var cart = { + order_reference: order.getOrderNo(), + total_amount: Math.round(amountInCent), + tax_amount: taxAmount, + currency: order.getCurrencyCode(), + billing_address: buildBillingAddressField(order), + in_store_cart_shipments: [], + items: buildCarItemField(order), + discounts: buildDiscountsField(order), + shipments: buildShipmentsField(order), + // metadata field is not used unless merchant is using old OCAPI + // flow with "sfcc_embedded_skip_ocapi_fetch_order" gate + // TODO (Alex P): Update this comment once gate removed + metadata: { + SFCCSessionID: getDwsidCookie() + } + }; + return cart; +} + +/** + * Build cart.shipment field + * @param {dw.order.Order} order SFCC Order + * @returns {Object} returns request's shipments + */ +function buildShipmentsField(order) { + var shipmentsField = []; + var shipments = order.getShipments(); + collections.forEach(shipments, function (shipment) { + var shippingAddress = shipment.getShippingAddress(); + var shipmentField = { + shipping_address: buildShippingAddressField(shippingAddress, order), + cost: getShipmentCostInCents(shipment), + service: shipment.getShippingMethod().getDisplayName() + }; + shipmentsField.push(shipmentField); + }); + + return shipmentsField; +} + +/** + * Map SFCC shipping cost to bolt shipping cost, + * we need to calculate the shipping cost without coupon price adjustment affected + * + * @param {dw.order.Shipment} shipment SFCC Product Line Item + * @returns {number} indicate if the product type can be processed by bolt + */ +function getShipmentCostInCents(shipment) { + var adjustedNetPrice = shipment.getAdjustedShippingTotalNetPrice().getValue(); + collections.forEach(shipment.getShippingPriceAdjustments(), function (priceAdjustment) { + if (priceAdjustment.isBasedOnCoupon()) { + adjustedNetPrice -= priceAdjustment.getPriceValue(); + } + }); + return Math.round(adjustedNetPrice * 100); +} + +/** + * Build shipping address field + * @param {dw.order.OrderAddress} shippingAddress address in Shipment object + * @param {dw.order.Order} order SFCC Order + * @returns {Object | null} new address object if existed + */ +function buildShippingAddressField(shippingAddress, order) { + if (shippingAddress == null || boltAccountUtils.isEmptyAddress(shippingAddress)) { + return null; + } + return { + first_name: shippingAddress.getFirstName() || '', + last_name: shippingAddress.getLastName() || '', + email: order.getCustomerEmail() || '', + phone: shippingAddress.getPhone() || '', + street_address1: shippingAddress.getAddress1() || '', + street_address2: shippingAddress.getAddress2() || '', + company: shippingAddress.getCompanyName() || '', + locality: shippingAddress.getCity() || '', + region: shippingAddress.getStateCode() || '', + postal_code: shippingAddress.getPostalCode() || '', + country_code: shippingAddress.getCountryCode() ? shippingAddress.getCountryCode().getValue().toString().toUpperCase() : '', + country: shippingAddress.getCountryCode() ? shippingAddress.getCountryCode().getDisplayValue() : '' + }; +} + +/** + * Returns billing address field + * + * @param {dw.order.Order} order SFCC Order + * @returns {Object | null} new address object if existed + */ +function buildBillingAddressField(order) { + var billingAddress = order.getBillingAddress(); + return { + street_address1: billingAddress.getAddress1() || '', + street_address2: billingAddress.getAddress2() || '', + company: billingAddress.getCompanyName() || '', + locality: billingAddress.getCity() || '', + region: billingAddress.getStateCode() || '', + postal_code: billingAddress.getPostalCode() || '', + country_code: billingAddress.getCountryCode() ? billingAddress.getCountryCode().getValue().toString().toUpperCase() : '', + country: billingAddress.getCountryCode() ? billingAddress.getCountryCode().getDisplayValue() : '', + name: billingAddress.getFullName() || '', + first_name: billingAddress.getFirstName() || '', + last_name: billingAddress.getLastName() || '', + phone_number: billingAddress.getPhone() || '', + email: order.getCustomerEmail() || '', + phone: billingAddress.getPhone() || '' + }; +} + +/** + * Build cart items field + * + * @param {dw.order.Order} order SFCC Order + * @returns {Object | null} new address object if existed + */ +function buildCarItemField(order) { + var cartItems = []; + var productLineItems = order.getProductLineItems(); + + collections.forEach(productLineItems, function (productLineItem) { + var item; + var totalAmount; + var unitPrice; + var quantity; + totalAmount = getProductTotalPriceInCents(productLineItem); + quantity = productLineItem.getQuantityValue(); + unitPrice = Math.round(totalAmount / quantity); + item = { + name: productLineItem.getProductName(), + reference: productLineItem.getProductID(), + quantity: quantity, + type: 'physical', + msrp: Math.round(productLineItem.getBasePrice().getValue() * 100), + total_amount: totalAmount, + unit_price: unitPrice + }; + + cartItems.push(item); + }); + + // GiftCertificate LineItems + var giftCertificateLineItems = order.getGiftCertificateLineItems(); + collections.forEach(giftCertificateLineItems, function (giftCertificateLineItem) { + var item = { + name: 'Gift Certificate', + reference: giftCertificateLineItem.getUUID(), + total_amount: Math.round(giftCertificateLineItem.getNetPrice().getValue() * 100), + unit_price: Math.round(giftCertificateLineItem.getPrice().getValue() * 100), + quantity: 1, + type: 'digital' + }; + cartItems.push(item); + }); + + return cartItems; +} + +/** + * build discounts field + * + * @param {dw.order.Order} order SFCC Order + * @returns {Object | null} new address object if existed + */ +function buildDiscountsField(order) { + var discountsField = []; + // Calculation for order level promotion + collections.forEach(order.getPriceAdjustments(), function (priceAdjustment) { + if (!priceAdjustment.isBasedOnCoupon()) { + var discountAmount = Math.abs(priceAdjustment.getPriceValue()); + var discountAmountInCents = Math.round(discountAmount * 100); + var description = priceAdjustment.getPromotionID(); + var promotion = priceAdjustment.getPromotion(); + if (promotion != null && 'cartMessage' in promotion.custom && promotion.custom.cartMessage != null) { + description = promotion.custom.cartMessage; + } + var promotionField = { + amount: discountAmountInCents, + description: description || '' + }; + discountsField.push(promotionField); + } + }); + // Calculation for coupon + var couponLineItems = order.getCouponLineItems(); + collections.forEach(couponLineItems, function (couponLineItem) { + if (!couponLineItem.isApplied()) { + return; + } + var couponField; + var couponCode = couponLineItem.getCouponCode(); + var CouponAmountInCents = calculateCouponAmountInCent(couponCode, order); + couponField = { + amount: CouponAmountInCents, + description: 'Coupon (' + couponCode + ')', + discount_category: 'coupon', + discount_code: couponCode, + reference: couponCode + }; + discountsField.push(couponField); + }); + // Calculation for Gift Cert Payment + collections.forEach(order.getGiftCertificatePaymentInstruments(), function (gcPaymentInstr) { + var giftPriceAdjustment = gcPaymentInstr.getPaymentTransaction().getAmount().getValue(); + if (giftPriceAdjustment > 0) { + var maskedGiftCertificateCode = gcPaymentInstr.getMaskedGiftCertificateCode(); + var gc = { + amount: Math.round(giftPriceAdjustment * 100), + description: 'Gift Certificate (' + maskedGiftCertificateCode + ')', + discount_category: 'giftcard', + discount_code: maskedGiftCertificateCode, + reference: maskedGiftCertificateCode + }; + discountsField.push(gc); + } + }); + + return discountsField; +} + +/** + * Calculate the sum of the price adjustments triggered by this coupon line item + * @param {string} couponCode SFCC Order + * @param {dw.order.Order} order SFCC Order + * @returns {number} the sum of the price adjustments triggered by this coupon line item + */ +function calculateCouponAmountInCent(couponCode, order) { + var couponAmount = 0; + // check order level price adjustments + collections.forEach(order.getPriceAdjustments(), function (priceAdjustment) { + var couponLineItem = priceAdjustment.getCouponLineItem(); + if (couponLineItem != null && couponLineItem.getCouponCode() === couponCode) { + couponAmount += priceAdjustment.getPriceValue(); + } + }); + // check shipment level price adjustments + collections.forEach(order.getShipments(), function (shipment) { + collections.forEach(shipment.getShippingPriceAdjustments(), function (priceAdjustment) { + var couponLineItem = priceAdjustment.getCouponLineItem(); + if (couponLineItem != null && couponLineItem.getCouponCode() === couponCode) { + couponAmount += priceAdjustment.getPriceValue(); + } + }); + }); + // check product level price adjustments + collections.forEach(order.getProductLineItems(), function (productLineItem) { + collections.forEach(productLineItem.getPriceAdjustments(), function (priceAdjustment) { + var couponLineItem = priceAdjustment.getCouponLineItem(); + if (couponLineItem != null && couponLineItem.getCouponCode() === couponCode) { + couponAmount += priceAdjustment.getPriceValue(); + } + }); + }); + + return Math.round(Math.abs(couponAmount) * 100); +} + +/** + * Get product total price, in order to map to bolt product price, + * we need to calculate the item total without coupon price adjustment affected + * + * @param {dw.order.ProductLineItem} productLineItem SFCC Product Line Item + * @returns {number} indicate if the product type can be processed by bolt + */ +function getProductTotalPriceInCents(productLineItem) { + var totalPrice = productLineItem.getAdjustedNetPrice().getValue(); + collections.forEach(productLineItem.getPriceAdjustments(), function (priceAdjustment) { + if (priceAdjustment.isBasedOnCoupon()) { + // use subtraction because price value for price adjustment is negative + totalPrice -= priceAdjustment.getPriceValue(); + } + }); + + var optionProductLineItemAmount = 0; + var optionProductLineItems = productLineItem.getOptionProductLineItems(); + collections.forEach(optionProductLineItems, function (optionProductLineItem) { + optionProductLineItemAmount += optionProductLineItem.getAdjustedNetPrice().getValue(); + }); + if (optionProductLineItemAmount > 0) { + totalPrice += optionProductLineItemAmount; + } + return Math.round(totalPrice * 100); +} + +/** + * getDwsidCookie returns DW Session ID from cookie + * @return {string} DW Session ID + */ +function getDwsidCookie() { + var cookies = request.getHttpCookies(); + + for (var i = 0; i < cookies.cookieCount; i++) { // eslint-disable-line no-plusplus + if (cookies[i].name === 'dwsid') { + return cookies[i].value; + } + } + + return ''; +} diff --git a/test/mocks/bolt/boltAccountUtils.js b/test/mocks/bolt/boltAccountUtils.js index 00136d8..cdb706c 100644 --- a/test/mocks/bolt/boltAccountUtils.js +++ b/test/mocks/bolt/boltAccountUtils.js @@ -20,13 +20,13 @@ function saveCardToBolt() { } } -function saveAddressToBolt() { - +function isEmptyAddress(){ + return false; } module.exports = { checkEmptyValue: checkEmptyValue, getBoltPayment: getBoltPayment, saveCardToBolt: saveCardToBolt, - saveAddressToBolt: saveAddressToBolt + isEmptyAddress: isEmptyAddress }; \ No newline at end of file diff --git a/test/mocks/bolt/boltPayAuthRequestBuilder.js b/test/mocks/bolt/boltPayAuthRequestBuilder.js new file mode 100644 index 0000000..e877760 --- /dev/null +++ b/test/mocks/bolt/boltPayAuthRequestBuilder.js @@ -0,0 +1,124 @@ +'use strict'; + +var request = { + "cart": { + "order_reference": "00000708", + "total_amount": 58791, + "tax_amount": 2800, + "currency": "USD", + "billing_address": { + "street_address1": "1 infinite loop", + "street_address2": "", + "locality": "cupertino", + "region": "CA", + "postal_code": "95014", + "country_code": "US", + "country": "United States", + "name": "justin xie", + "first_name": "justin", + "last_name": "xie", + "phone_number": "9234567890", + "email": "jxie@bolt.com", + "phone": "9234567890" + }, + "in_store_cart_shipments": [], + "items": [ + { + "name": "Silver Chandler Earring", + "reference": "013742000269M", + "quantity": 1, + "type": "physical", + "image_url": "https://zzgv-004.dx.commercecloud.salesforce.com/on/demandware.static/-/Sites-apparel-m-catalog/default/dw6875bcc9/images/large/PG.60108564.JJNY2XX.PZ.jpg", + "total_amount": 2900, + "unit_price": 2900 + }, + { + "name": "Sony Playstation 3 Game Console", + "reference": "sony-ps3-consoleM", + "quantity": 2, + "type": "physical", + "image_url": "https://zzgv-004.dx.commercecloud.salesforce.com/on/demandware.static/-/Sites-electronics-m-catalog/default/dw42e685c0/images/large/sony-ps3-console.jpg", + "total_amount": 75996, + "unit_price": 37998 + } + ], + "discounts": [ + { + "amount": 1000, + "description": "order-10off-auto" + }, + { + "amount": 22400, + "description": "Coupon (discount)", + "discount_category": "coupon", + "discount_code": "discount", + "reference": "discount" + } + ], + "shipments": [ + { + "shipping_address": { + "first_name": "justin", + "last_name": "xie", + "email": "jxie@bolt.com", + "phone": "9234567890", + "street_address1": "1 infinite loop", + "street_address2": "", + "locality": "cupertino", + "region": "CA", + "postal_code": "95014", + "country_code": "US", + "country": "United States" + }, + "cost": 495, + "service": "Standard" + } + ] + }, + "division_id": "-GZW2YSKRCU1", + "source": "direct_payments", + "user_identifier": { + "email": "jxie@bolt.com", + "phone": "9234567890" + }, + "user_identity": { + "first_name": "justin", + "last_name": "xie" + }, + "create_bolt_account": false, + "auto_capture": true, + "credit_card": { + "token": "77d9ee7dcacfe9be5a9fa93966b8d83fc8c3ebf38e15617b538c1650176ecb55", + "last4": "1111", + "bin": "411111", + "billing_address": { + "street_address1": "1 infinite loop", + "street_address2": "", + "locality": "cupertino", + "region": "CA", + "postal_code": "95014", + "country_code": "US", + "country": "United States", + "name": "justin xie", + "first_name": "justin", + "last_name": "xie", + "phone_number": "9234567890", + "email": "jxie@bolt.com", + "phone": "9234567890" + }, + "number": "", + "expiration": "2030-03", + "postal_code": "95014", + "token_type": "bolt" + } +}; + +function build() { + return { + authRequest: request, + error: false + }; +} +module.exports = { + build: build +}; diff --git a/test/mocks/newOrder.js b/test/mocks/newOrder.js new file mode 100644 index 0000000..2d34506 --- /dev/null +++ b/test/mocks/newOrder.js @@ -0,0 +1,580 @@ +'use strict'; + +var ArrayList = require('./dw.util.Collection'); + +var billingAddress = { + getCompanyName(){ + return 'bolt'; + }, + getPostalCode(){ + return '02135'; + }, + getAddress1(){ + return '65 May Lane'; + }, + getAddress2(){ + return ''; + }, + getCity(){ + return 'Allston'; + }, + getStateCode(){ + return 'MA'; + }, + getCountryCode(){ + return { + getDisplayValue(){ + return 'United States'; + }, + getValue(){ + return 'US'; + } + } + }, + getFirstName(){ + return 'Amanda'; + }, + getLastName(){ + return 'Jones'; + }, + getPhone(){ + return '617-555-1234'; + }, + getFullName(){ + return 'Amanda Jones'; + } +}; + +var standardShippingMethod = { + getDisplayName(){ + return 'Standard' + }, + getID(){ + return 'Standard' + } +}; + +var instoreShippingMethod = { + getDisplayName(){ + return 'ShipToStore' + }, + getID(){ + return 'ShipToStore' + } +}; + +var shippingAddress = { + address1 : 'address1', + address2 : '', + city : 'city', + stateCode : 'MA', + postalCode : '02135', + countryCode : { value: 'us' }, + firstName : 'firstName', + lastName : 'lastName', + phone : '617-555-1234', + custom:{ + boltAddressId : 'boltAddressId' + }, + getCompanyName(){ + return 'bolt'; + }, + getPostalCode(){ + return this.postalCode; + }, + getAddress1(){ + return this.address1; + }, + getAddress2(){ + return this.address2; + }, + getCity(){ + return this.city; + }, + getStateCode(){ + return this.postalCode; + }, + getCountryCode(){ + return { + getDisplayValue(){ + return 'United States'; + }, + getValue(){ + return 'US'; + } + } + }, + getFirstName(){ + return this.firstName; + }, + getLastName(){ + return this.lastName; + }, + getPhone(){ + return this.phone; + } +}; + +var optionProductLineItem = { + getOptionID(){ + return 'optionID'; + }, + getAdjustedNetPrice(){ + return { + getValue(){ + return 59.98; + } + }; + }, + getAdjustedPrice(){ + return { + getValue(){ + return 59.98; + } + }; + }, + getPriceAdjustments(){ + return new ArrayList([]); + } +}; + +var appliedCouponLineItem = { + couponCode : 'discount', + getCouponCode(){ + return this.couponCode; + }, + isApplied(){ + return true; + } +} + +var unappliedCouponLineItem = { + couponCode : 'unapplied', + getCouponCode(){ + return this.couponCode; + }, + isApplied(){ + return false; + } +} + +var orderPriceAdjustments = new ArrayList([ + { + getCouponLineItem(){ + return null; + }, + isBasedOnCoupon(){ + return false; + }, + getPrice(){ + return -10.00; + }, + getPriceValue(){ + return -10.00; + }, + getPromotionID(){ + return 'orderPromotionAuto' + }, + getPromotion(){ + return orderPromotionAuto; + } + }, + { + getCouponLineItem(){ + return appliedCouponLineItem; + }, + isBasedOnCoupon(){ + return true; + }, + getPrice(){ + return -20.00; + }, + getPriceValue(){ + return -20.00; + }, + getPromotionID(){ + return 'promotionID2' + }, + getPromotion(){ + return orderPromotionCoupon; + } + } +]); + +var orderPromotionAuto = { + custom:{ + cartMessage: 'orderPromotionAuto' + } +}; + +var orderPromotionCoupon = { + custom:{ + cartMessage: 'orderPromotionCoupon' + } +} + + +var couponLineItems = new ArrayList([appliedCouponLineItem, unappliedCouponLineItem]); + +var giftCertificatePaymentInstruments = new ArrayList([ + { + getPaymentTransaction(){ + return { + getAmount(){ + return { + getValue(){ + return 5.00; + } + } + } + } + }, + getMaskedGiftCertificateCode(){ + return '************1234'; + } + } +]); + +var shippingPriceAdjustments = new ArrayList([ + { + getCouponLineItem(){ + return null; + }, + isBasedOnCoupon(){ + return false; + }, + getPrice(){ + return -1.00; + }, + getPriceValue(){ + return -1.00; + } + }, + { + getCouponLineItem(){ + return appliedCouponLineItem; + }, + isBasedOnCoupon(){ + return true; + }, + getPrice(){ + return -2.00; + }, + getPriceValue(){ + return -2.00; + } + } +]); + +var sonyPlayStationProductAdjustments = new ArrayList([ + { + getCouponLineItem(){ + return null; + }, + isBasedOnCoupon(){ + return false; + }, + getPrice(){ + return -100.00; + }, + getPriceValue(){ + return -100.00; + } + }, + { + getCouponLineItem(){ + return appliedCouponLineItem; + }, + isBasedOnCoupon(){ + return true; + }, + getPrice(){ + return -200.00; + }, + getPriceValue(){ + return -200.00; + } + } +]); + +var earringProductAdjustments = new ArrayList([ + { + getCouponLineItem(){ + return null; + }, + isBasedOnCoupon(){ + return false; + }, + getPrice(){ + return -1.00; + }, + getPriceValue(){ + return -1.00; + } + }, + { + getCouponLineItem(){ + return appliedCouponLineItem; + }, + isBasedOnCoupon(){ + return true; + }, + getPrice(){ + return -2.00; + }, + getPriceValue(){ + return -2.00; + } + } +]); + +var sonyPlayStationProduct = { + getProductName(){ + return 'Sony PlayStation'; + }, + getProductID(){ + return 'sonyPlayStation'; + }, + getOptionID(){ + return null; + }, + getAdjustedNetPrice(){ + return { + getValue(){ + return 499.98; + } + }; + }, + getBasePrice(){ + return { + getValue(){ + return 399.99; + } + }; + }, + getShipment(){ + return defaultShipment; + }, + getPriceAdjustments(){ + return sonyPlayStationProductAdjustments; + }, + getOptionProductLineItems(){ + return new ArrayList([optionProductLineItem]); + }, + getQuantityValue(){ + return 2; + }, + getProduct(){ + return { + getImage(){ + return { + getAbsURL(){ + return 'image_URL'; + } + }; + } + }; + } +}; + +var earringProduct = { + getProductName(){ + return 'Earring'; + }, + getProductID(){ + return 'earring'; + }, + getOptionID(){ + return null; + }, + getAdjustedNetPrice(){ + return { + getValue(){ + return 27.00; + } + }; + }, + getBasePrice(){ + return { + getValue(){ + return 30.00; + } + }; + }, + getShipment(){ + return inStoreShipment; + }, + getPriceAdjustments(){ + return earringProductAdjustments; + }, + getOptionProductLineItems(){ + return new ArrayList([]); + }, + getQuantityValue(){ + return 1; + }, + getProduct(){ + return { + getImage(){ + return { + getAbsURL(){ + return 'image_URL'; + } + }; + } + }; + } +}; + +var productLineItems = new ArrayList([ + sonyPlayStationProduct, + earringProduct +]); + +var defaultShipment = { + getShippingAddress(){ + return shippingAddress; + }, + isGift(){ + return false; + }, + getShippingMethod(){ + return standardShippingMethod; + }, + getShippingMethodID(){ + return standardShippingMethod.getID(); + }, + getAdjustedShippingTotalNetPrice(){ + return { + getValue(){ + return 2.95; + } + }; + }, + getShippingPriceAdjustments(){ + return shippingPriceAdjustments; + } +}; + +var inStoreShipment = { + custom:{ + fromStoreId : 'store1' + }, + getShippingAddress(){ + return shippingAddress; + }, + isGift(){ + return false; + }, + getShippingMethod(){ + return instoreShippingMethod; + }, + getShippingMethodID(){ + return instoreShippingMethod.getID(); + }, + getAdjustedShippingTotalNetPrice(){ + return { + getValue(){ + return 0.00; + } + }; + }, + getShippingPriceAdjustments(){ + return new ArrayList([]); + } +}; + +var giftCertificateLineItems = new ArrayList([ + { + getRecipientEmail(){ + return 'Recipient@test.com'; + }, + getRecipientName(){ + return 'Recipient'; + }, + getSenderName(){ + return 'Sender'; + }, + getMessage(){ + return 'message'; + }, + getLineItemText(){ + return 'text'; + }, + getUUID(){ + return 'giftCert'; + }, + getGrossPrice(){ + return { + getValue(){ + return 20.00; + } + }; + }, + getNetPrice(){ + return { + getValue(){ + return 20.00; + } + }; + }, + getPrice(){ + return { + getValue(){ + return 20.00; + } + }; + } + } +]); + +function Order(){ + return { + getGiftCertificatePaymentInstruments(){ + return giftCertificatePaymentInstruments; + }, + getCouponLineItems(){ + return couponLineItems; + }, + + getPriceAdjustments(){ + return orderPriceAdjustments; + }, + getGiftCertificateLineItems(){ + return giftCertificateLineItems; + }, + getProductLineItems(){ + return productLineItems; + }, + getCustomerEmail(){ + return 'test@test.com'; + }, + getBillingAddress(){ + return billingAddress; + }, + getDefaultShipment(){ + return defaultShipment; + }, + getShipments(){ + return new ArrayList([ + defaultShipment, + inStoreShipment + ]); + }, + getTotalTax(){ + return { + getValue(){ + return 28.00; + } + } + }, + getOrderNo(){ + return '1000000000' + }, + getCurrencyCode(){ + return 'USD' + } + }; +} + +module.exports = Order; \ No newline at end of file diff --git a/test/mocks/paymentInstrument.js b/test/mocks/paymentInstrument.js index ddbfd09..a4f66f4 100644 --- a/test/mocks/paymentInstrument.js +++ b/test/mocks/paymentInstrument.js @@ -13,12 +13,22 @@ module.exports = { paymentTransaction : { paymentProcessor : '', transactionID : '', + amount: { + value: 10.00, + getValue(){ + return this.value; + } + }, setPaymentProcessor(val){ this.paymentProcessor = val; }, setTransactionID(val){ this.transactionID = val; + }, + getAmount(){ + return this.amount; } + }, getPaymentTransaction() { return this.paymentTransaction; diff --git a/test/unit/int_bolt_embedded_sfra/script/hooks/payment/processor/bolt_pay.js b/test/unit/int_bolt_embedded_sfra/script/hooks/payment/processor/bolt_pay.js index 155cdea..df3e97a 100644 --- a/test/unit/int_bolt_embedded_sfra/script/hooks/payment/processor/bolt_pay.js +++ b/test/unit/int_bolt_embedded_sfra/script/hooks/payment/processor/bolt_pay.js @@ -56,7 +56,8 @@ describe('bolt pay payment processor', function () { '~/cartridge/scripts/util/constants':require('../../../../../../../cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/constants'), '~/cartridge/scripts/util/boltAccountUtils': boltAccountUtilsMock, '~/cartridge/scripts/util/boltLogUtils':require('../../../../../../mocks/bolt/boltLogUtils'), - '~/cartridge/scripts/util/boltPaymentUtils':require('../../../../../../mocks/bolt/boltPaymentUtils') + '~/cartridge/scripts/util/boltPaymentUtils':require('../../../../../../mocks/bolt/boltPaymentUtils'), + '~/cartridge/scripts/util/boltPayAuthRequestBuilder':require('../../../../../../mocks/bolt/boltPayAuthRequestBuilder') }; beforeEach(function () { diff --git a/test/unit/int_bolt_embedded_sfra/script/util/boltAccountUtils.js b/test/unit/int_bolt_embedded_sfra/script/util/boltAccountUtils.js index 0969d56..40d3ceb 100644 --- a/test/unit/int_bolt_embedded_sfra/script/util/boltAccountUtils.js +++ b/test/unit/int_bolt_embedded_sfra/script/util/boltAccountUtils.js @@ -299,42 +299,4 @@ describe('boltAccountUtils', function () { }); }); - describe('saveAddressToBolt', function () { - var order; - beforeEach(function () { - order = require('../../../../mocks/order'); - oAuthTokenMock = 'oAuthToken'; - logErrorSpy.resetHistory(); - }); - - it('should trigger log.error function when OAuth token is missing', function () { - oAuthTokenMock = null; - boltAccountUtils.saveAddressToBolt(order); - expect(logErrorSpy.callCount).to.be.equal(1); - }); - - it('should save address to bolt shopper account and not trigger log.error function', function () { - responseMock = { - status : 0 - }; - boltAccountUtils.saveAddressToBolt(order); - expect(logErrorSpy.callCount).to.be.equal(0); - }); - - it('should trigger log.error function when save address response returns error', function () { - responseMock = { - status : 1 - }; - boltAccountUtils.saveAddressToBolt(order); - expect(logErrorSpy.callCount).to.be.equal(1); - }); - - it('should trigger log.error function when exception occurs', function () { - order.getDefaultShipment = function (){ - throw new Error('Not able to get default shipment'); - }; - boltAccountUtils.saveAddressToBolt(order); - expect(logErrorSpy.callCount).to.be.equal(1); - }); - }); }); \ No newline at end of file diff --git a/test/unit/int_bolt_embedded_sfra/script/util/boltPayAuthRequestBuilder.js b/test/unit/int_bolt_embedded_sfra/script/util/boltPayAuthRequestBuilder.js new file mode 100644 index 0000000..c060d0d --- /dev/null +++ b/test/unit/int_bolt_embedded_sfra/script/util/boltPayAuthRequestBuilder.js @@ -0,0 +1,97 @@ +'use strict'; + +var chai = require('chai'); +var assert = chai.assert; +var expect = chai.expect; +var proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +var sinon = require('sinon'); +var BasketMgr = require('../../../../mocks/dw/order/BasketMgr'); +var ArrayList = require('../../../../mocks/dw.util.Collection.js'); + +var collections = proxyquire( + '../../../../../../storefront-reference-architecture/cartridges/app_storefront_base/cartridge/scripts/util/collections', + { + 'dw/util/ArrayList': ArrayList + } +); + +var loginAsBoltUserStub = sinon.stub(); + + +describe('boltPayAuthRequestBuilder', function () { + var boltPayAuthRequestBuilder; + var boltAccountUtilsMock = require('../../../../mocks/bolt/boltAccountUtils.js'); + boltAccountUtilsMock.loginAsBoltUser = loginAsBoltUserStub; + // Add spy to log counts of triggering log.error + var logErrorSpy = sinon.spy(); + var boltLogUtilsMock = { + getLogger() { + return { + debug() {}, + warn() {}, + info() {}, + error : logErrorSpy + }; + } + }; + var boltPayAuthRequestBuilderPath = '../../../../../cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/boltPayAuthRequestBuilder'; + var boltPayAuthRequestBuilderRequires = { + /* API Includes */ + 'dw/util/StringUtils' : require('../../../../mocks/dw/util/StringUtils'), + 'dw/system/Site' : require('../../../../mocks/dw/system/Site'), + /* Script Modules */ + '*/cartridge/scripts/util/collections': collections, + '~/cartridge/scripts/util/boltAccountUtils': boltAccountUtilsMock, + '~/cartridge/scripts/util/constants': require('../../../../../cartridges/int_bolt_embedded_sfra/cartridge/scripts/util/constants'), + '~/cartridge/scripts/util/boltLogUtils': boltLogUtilsMock, + }; + var Order, paymentInstrument; + Order = require('../../../../mocks/newOrder'); + + beforeEach(function () { + paymentInstrument = require('../../../../mocks/paymentInstrument'); + loginAsBoltUserStub.reset(); + boltPayAuthRequestBuilder = proxyquire(boltPayAuthRequestBuilderPath, boltPayAuthRequestBuilderRequires); + }); + + describe('build', function () { + it('should return expect auth request body and match all the numbers', function () { + loginAsBoltUserStub.returns(false); + var testOrder = new Order(); + var authRequestObj = boltPayAuthRequestBuilder.build(testOrder, paymentInstrument); + + assert.isNotNull(authRequestObj.authRequest); + var authRequest = authRequestObj.authRequest; + assert.equal(authRequest.cart.tax_amount, 2800); + assert.equal(authRequest.cart.discounts[0].amount, 1000); + assert.equal(authRequest.cart.discounts[1].amount, 22400); + assert.equal(authRequest.cart.items[0].total_amount, 75996); + assert.equal(authRequest.cart.items[0].unit_price, 37998); + assert.equal(authRequest.cart.items[1].total_amount, 2900); + assert.equal(authRequest.cart.items[1].unit_price, 2900); + assert.equal(authRequest.cart.shipments[0].cost, 495); + }); + + it('should return error if payment instrument is missing', function () { + loginAsBoltUserStub.returns(false); + var testOrder = new Order(); + var authRequestObj = boltPayAuthRequestBuilder.build(testOrder, null); + assert.isTrue(authRequestObj.error); + assert.equal(authRequestObj.errorMsg, 'Missing payment instrument.'); + assert.isUndefined(authRequestObj.authRequest); + }); + it('should return error if billing address is missing', function () { + loginAsBoltUserStub.returns(false); + var testOrder = new Order(); + testOrder.getBillingAddress = function(){ + return null; + } + var authRequestObj = boltPayAuthRequestBuilder.build(testOrder, paymentInstrument); + assert.isTrue(authRequestObj.error); + assert.equal(authRequestObj.errorMsg, 'SFCC basket has not billing address.'); + assert.isUndefined(authRequestObj.authRequest); + }); + + }); + +}); \ No newline at end of file