diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/config/constants.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/config/constants.js
index bbb8c8750..c0f418e29 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/config/constants.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/config/constants.js
@@ -32,6 +32,7 @@ module.exports = {
PAYMENTMETHODS: {
APPLEPAY: 'applepay',
AMAZONPAY: 'amazonpay',
+ PAYPAL: 'paypal',
},
CAN_SKIP_SUMMARY_PAGE: ['applepay', 'cashapp'],
@@ -47,6 +48,7 @@ module.exports = {
CHECKBALANCE: 'AdyenCheckBalance',
CANCELPARTIALPAYMENTORDER: 'AdyenCancelPartialPaymentOrder',
PARTIALPAYMENTSORDER: 'AdyenPartialPaymentsOrder',
+ PAYPALUPDATEORDER: 'AdyenPaypalUpdateOrder',
},
CONTRACT: {
ONECLICK: 'ONECLICK',
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/logs/adyenCustomLogs.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/logs/adyenCustomLogs.js
index cbae1a2d4..96dc8de43 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/logs/adyenCustomLogs.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/logs/adyenCustomLogs.js
@@ -1,19 +1,21 @@
const Logger = require('dw/system/Logger');
-function fatal_log(msg) {
- return Logger.getLogger('Adyen_fatal', 'Adyen').fatal(msg);
+function fatal_log(msg, error) {
+ const logMsg = [msg, error?.toString(), error?.stack].join('\n').trim();
+ Logger.getLogger('Adyen_fatal', 'Adyen').fatal(logMsg);
}
-function error_log(msg) {
- return Logger.getLogger('Adyen_error', 'Adyen').error(msg);
+function error_log(msg, error) {
+ const logMsg = [msg, error?.toString(), error?.stack].join('\n').trim();
+ Logger.getLogger('Adyen_error', 'Adyen').error(logMsg);
}
function debug_log(msg) {
- return Logger.getLogger('Adyen_debug', 'Adyen').debug(msg);
+ Logger.getLogger('Adyen_debug', 'Adyen').debug(msg);
}
function info_log(msg) {
- return Logger.getLogger('Adyen_info', 'Adyen').info(msg);
+ Logger.getLogger('Adyen_info', 'Adyen').info(msg);
}
module.exports = {
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/selectShippingMethods.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/selectShippingMethods.test.js
index 9feb161ac..885126b55 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/selectShippingMethods.test.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/selectShippingMethods.test.js
@@ -13,10 +13,7 @@ describe('callSelectShippingMethod', () => {
jest.clearAllMocks();
req = {
- querystring: {
- },
- form: {
- },
+ body: JSON.stringify({})
};
res = {
@@ -26,6 +23,10 @@ describe('callSelectShippingMethod', () => {
next = jest.fn();
});
+ afterEach(() => {
+ jest.clearAllMocks();
+ })
+
it('should handle the case when there is no current basket', () => {
currentBasket = BasketMgr.getCurrentBasket.mockReturnValueOnce(null);
callSelectShippingMethod(req, res, next);
@@ -37,13 +38,13 @@ describe('callSelectShippingMethod', () => {
});
it('should handle the case when there is an error selecting the shipping method', () => {
- req.querystring.shipmentUUID = 'mocked_uuid';
+ req.body = JSON.stringify({shipmentUUID: 'mocked_uuid'});
currentBasket = {
defaultShipment: {},
};
BasketMgr.getCurrentBasket.mockReturnValueOnce(currentBasket.defaultShipment);
shippingHelper.getShipmentByUUID.mockReturnValueOnce(currentBasket.defaultShipment);
-
+
callSelectShippingMethod(req, res, next);
expect(res.setStatusCode).toHaveBeenCalledWith(500);
@@ -75,8 +76,8 @@ describe('callSelectShippingMethod', () => {
someMethod: jest.fn(),
};
CartModel.mockReturnValueOnce(basketModelInstance);
-
- callSelectShippingMethod(req, res, next);
+
+ callSelectShippingMethod(req, res, next);
expect(res.json).toHaveBeenCalledWith({
...basketModelInstance,
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/shippingMethods.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/shippingMethods.test.js
index 44fbf2982..dc170df23 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/shippingMethods.test.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/__tests__/shippingMethods.test.js
@@ -13,22 +13,19 @@ beforeEach(() => {
jest.clearAllMocks();
req = {
- querystring: {
+ body: JSON.stringify({address:{
city: 'Amsterdam',
countryCode: 'NL',
stateCode: 'AMS',
postalCode: '1001',
shipmentUUID: 'mocked_uuid',
- },
- locale: { id: 'nl_NL' },
- form: {
- methodID: 'mocked_methodID',
- },
+ }}),
};
res = {
redirect: jest.fn(),
json: jest.fn(),
+ setStatusCode: jest.fn(),
};
});
@@ -39,6 +36,25 @@ afterEach(() => {
describe('Shipping methods', () => {
it('Should return available shipping methods', () => {
const Logger = require('../../../../../../../../jest/__mocks__/dw/system/Logger');
+ currentBasket = {
+ getDefaultShipment: jest.fn(() => {
+ return {
+ shippingAddress: {
+ setCity: jest.fn(),
+ setPostalCode: jest.fn(),
+ setStateCode: jest.fn(),
+ setCountryCode: jest.fn(),
+ }}
+ }),
+ getTotalGrossPrice: jest.fn(() => {
+ return {
+ currencyCode: 'EUR',
+ value: '1000'
+ }
+ }),
+ updateTotals: jest.fn(),
+ };
+ BasketMgr.getCurrentBasket.mockReturnValueOnce(currentBasket);
callGetShippingMethods(req, res, next);
expect(AdyenHelper.getApplicableShippingMethods).toHaveBeenCalledTimes(1);
expect(res.json).toHaveBeenCalledWith({
@@ -53,8 +69,14 @@ describe('Shipping methods', () => {
new Logger.error('error'),
);
callGetShippingMethods(req, res, next);
- expect(res.json).not.toHaveBeenCalled();
+ expect(res.setStatusCode).toHaveBeenCalledWith(500);
+ expect(res.json).toHaveBeenCalledWith({
+ errorMessage: 'mocked_error.cannot.find.shipping.methods',
+ });
+ expect(next).toHaveBeenCalled();
});
+});
+
it('Should update shipping address for the basket', () => {
const Logger = require('../../../../../../../../jest/__mocks__/dw/system/Logger');
const setCityMock = jest.fn()
@@ -79,4 +101,4 @@ describe('Shipping methods', () => {
expect(setCountryCodeMock).toHaveBeenCalledWith('NL');
expect(Logger.error.mock.calls.length).toBe(0);
});
-});
\ No newline at end of file
+
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/handleCheckoutReview.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/handleCheckoutReview.test.js
new file mode 100644
index 000000000..e5e39982a
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/handleCheckoutReview.test.js
@@ -0,0 +1,91 @@
+/* eslint-disable global-require */
+const BasketMgr = require('dw/order/BasketMgr');
+const URLUtils = require('dw/web/URLUtils');
+const validationHelpers = require('*/cartridge/scripts/helpers/basketValidationHelpers');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+
+let res;
+let req;
+const next = jest.fn();
+
+const handleCheckoutReview = require('../handleCheckoutReview');
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ currentBasket = {
+ getPaymentInstruments: jest.fn(() => ([{
+ custom: { adyenPaymentMethod: '' }
+ }])),
+ };
+
+ req = {
+ form: {
+ data: JSON.stringify({details: 'test_paymentsDetails'}),
+ },
+ currentCustomer: {
+ raw: ''
+ },
+ locale: {
+ id: 'NL'
+ },
+ session: {
+ privacyCache: {
+ get: jest.fn()
+ }
+ }
+ };
+
+ res = {
+ redirect: jest.fn(),
+ render: jest.fn(),
+ setStatusCode: jest.fn(),
+ };
+ AdyenLogs.error_log = jest.fn();
+ URLUtils.url =jest.fn();
+ BasketMgr.getCurrentBasket.mockReturnValueOnce(currentBasket);
+});
+
+afterEach(() => {
+ jest.resetModules();
+});
+
+describe('Checkout Review controller', () => {
+ it('Should return Checkout Review page', () => {
+ handleCheckoutReview(req, res, next);
+ expect(res.render.mock.calls[0][0]).toBe('cart/checkoutReview');
+ expect(res.render.mock.calls[0][1]).toMatchObject( {
+ data: '{"details":"test_paymentsDetails"}',
+ showConfirmationUrl: expect.anything(),
+ order: expect.anything(),
+ customer: expect.anything(),
+ }
+ )
+ expect(AdyenLogs.error_log).not.toHaveBeenCalled();
+ expect(next).toHaveBeenCalled();
+ });
+
+ it('Should fail returning Checkout Review page when no state data is submitted', () => {
+ req.form = '';
+ handleCheckoutReview(req, res, next);
+ expect(AdyenLogs.error_log).toHaveBeenCalledTimes(1);
+ expect(res.redirect).toHaveBeenCalledTimes(1)
+ expect(URLUtils.url).toHaveBeenCalledWith("Error-ErrorCode", "err", "general");
+ expect(next).toHaveBeenCalled();
+ });
+ it('Should redirect to Cart if there is no current Basket', () => {
+ BasketMgr.getCurrentBasket = jest.fn().mockImplementationOnce(() => (''))
+ handleCheckoutReview(req, res, next);
+ expect(res.redirect).toHaveBeenCalledTimes(1)
+ expect(URLUtils.url).toHaveBeenCalledWith("Cart-Show");
+ expect(AdyenLogs.error_log).not.toHaveBeenCalled();
+ expect(next).toHaveBeenCalled();
+ });
+ it('Should redirect to Cart if product validation fails', () => {
+ validationHelpers.validateProducts = jest.fn(() => ({error: true}))
+ handleCheckoutReview(req, res, next);
+ expect(res.redirect).toHaveBeenCalledTimes(1)
+ expect(URLUtils.url).toHaveBeenCalledWith("Cart-Show");
+ expect(AdyenLogs.error_log).not.toHaveBeenCalled();
+ expect(next).toHaveBeenCalled();
+ });
+});
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/makeExpressPaymentDetailsCall.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/makeExpressPaymentDetailsCall.test.js
new file mode 100644
index 000000000..75dd0cf47
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/makeExpressPaymentDetailsCall.test.js
@@ -0,0 +1,57 @@
+/* eslint-disable global-require */
+const URLUtils = require('dw/web/URLUtils');
+const COHelpers = require('*/cartridge/scripts/checkout/checkoutHelpers');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
+
+let res;
+let req;
+const next = jest.fn();
+
+const makeExpressPaymentDetailsCall = require('../makeExpressPaymentDetailsCall');
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ req = {
+ body: JSON.stringify({data: {}})
+ };
+
+ res = {
+ redirect: jest.fn(),
+ json: jest.fn(),
+ setStatusCode: jest.fn(),
+ };
+ AdyenLogs.error_log = jest.fn();
+ AdyenLogs.fatal_log = jest.fn();
+ URLUtils.url = jest.fn();
+});
+
+afterEach(() => {
+ jest.resetModules();
+});
+
+describe('Express Payment Details controller', () => {
+ it('Should return response when payment details call is successful', () => {
+ makeExpressPaymentDetailsCall(req, res, next);
+ expect(res.json).toHaveBeenCalledWith({"orderNo": "mocked_orderNo", "orderToken": "mocked_orderToken"});
+ expect(AdyenLogs.error_log).not.toHaveBeenCalled();
+ expect(next).toHaveBeenCalled();
+ });
+
+ it('Should return error response when payment details call is not successful', () => {
+ adyenCheckout.doPaymentsDetailsCall = jest.fn().mockImplementationOnce(() => {throw new Error('unexpected mock error')});
+ makeExpressPaymentDetailsCall(req, res, next);
+ expect(AdyenLogs.error_log).toHaveBeenCalledTimes(1);
+ expect(res.redirect).toHaveBeenCalledTimes(1);
+ expect(URLUtils.url).toHaveBeenCalledWith('Error-ErrorCode', 'err', 'general');
+ expect(next).toHaveBeenCalled();
+ });
+ it('Should return error response when place Order is not successful', () => {
+ COHelpers.placeOrder = jest.fn(() => ({error: true}))
+ makeExpressPaymentDetailsCall(req, res, next);
+ expect(AdyenLogs.error_log).toHaveBeenCalledTimes(1);
+ expect(res.redirect).toHaveBeenCalledTimes(1);
+ expect(URLUtils.url).toHaveBeenCalledWith('Error-ErrorCode', 'err', 'general');
+ expect(next).toHaveBeenCalled();
+ });
+});
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/makeExpressPaymentsCall.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/makeExpressPaymentsCall.test.js
new file mode 100644
index 000000000..d202459e8
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/makeExpressPaymentsCall.test.js
@@ -0,0 +1,46 @@
+/* eslint-disable global-require */
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
+
+let res;
+let req;
+const next = jest.fn();
+
+const makeExpressPaymentsCall = require('../makeExpressPaymentsCall');
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ req = {
+ body: JSON.stringify({})
+ };
+
+ res = {
+ redirect: jest.fn(),
+ json: jest.fn(),
+ setStatusCode: jest.fn(),
+ };
+ AdyenLogs.error_log = jest.fn();
+ AdyenLogs.fatal_log = jest.fn();
+});
+
+afterEach(() => {
+ jest.resetModules();
+});
+
+describe('Express Payments controller', () => {
+ it('Should return response when payments call is successful', () => {
+ makeExpressPaymentsCall(req, res, next);
+ expect(res.json).toHaveBeenCalledWith({"pspReference": "mocked_pspReference"});
+ expect(AdyenLogs.error_log).not.toHaveBeenCalled();
+ expect(next).toHaveBeenCalled();
+ });
+
+ it('Should return error response when payments call is not successful', () => {
+ adyenCheckout.doPaymentsCall = jest.fn(() => {throw new Error('unexpected mock error')});
+ makeExpressPaymentsCall(req, res, next);
+ expect(AdyenLogs.fatal_log).toHaveBeenCalledTimes(1);
+ expect(res.json).toHaveBeenCalledWith({"errorMessage": "mocked_error.express.paypal.payments"})
+ expect(res.setStatusCode).toHaveBeenCalledWith(500);
+ expect(next).toHaveBeenCalled();
+ });
+});
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/saveShopperData.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/saveShopperData.test.js
new file mode 100644
index 000000000..1d9588311
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/__tests__/saveShopperData.test.js
@@ -0,0 +1,46 @@
+/* eslint-disable global-require */
+const URLUtils = require('dw/web/URLUtils');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+
+let res;
+let req;
+const next = jest.fn();
+
+const saveShopperData = require('../saveShopperData');
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ req = {
+ form: {shopperDetails: JSON.stringify({})}
+ };
+
+ res = {
+ redirect: jest.fn(),
+ json: jest.fn(),
+ setStatusCode: jest.fn(),
+ };
+ AdyenLogs.error_log = jest.fn();
+ URLUtils.url = jest.fn();
+});
+
+afterEach(() => {
+ jest.resetModules();
+});
+
+describe('Save Shopper controller', () => {
+ it('Should return response when Save Shopper call is successful', () => {
+ saveShopperData(req, res, next);
+ expect(res.json).toHaveBeenCalledWith({"success": true});
+ expect(AdyenLogs.error_log).not.toHaveBeenCalled();
+ expect(next).toHaveBeenCalled();
+ });
+
+ it('Should return response when Save Shopper call is not successful', () => {
+ res.json = jest.fn(() => {throw new Error('unexpected mock error')});
+ saveShopperData(req, res, next);
+ expect(AdyenLogs.error_log).toHaveBeenCalledTimes(1);
+ expect(res.redirect).toHaveBeenCalledTimes(1);
+ expect(URLUtils.url).toHaveBeenCalledWith('Error-ErrorCode', 'err', 'general');
+ expect(next).toHaveBeenCalled();
+ });
+});
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/handleCheckoutReview.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/handleCheckoutReview.js
new file mode 100644
index 000000000..12b55162b
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/handleCheckoutReview.js
@@ -0,0 +1,90 @@
+const URLUtils = require('dw/web/URLUtils');
+const BasketMgr = require('dw/order/BasketMgr');
+const Locale = require('dw/util/Locale');
+const Transaction = require('dw/system/Transaction');
+const AccountModel = require('*/cartridge/models/account');
+const OrderModel = require('*/cartridge/models/order');
+const validationHelpers = require('*/cartridge/scripts/helpers/basketValidationHelpers');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
+const paypalHelper = require('*/cartridge/adyen/utils/paypalHelper');
+
+/**
+ * Sets Shipping and Billing address for the basket,
+ * also updated payment method on the paymentInstrument of Basket.
+ * @param {dw.order.Basket} currentBasket - the current basket
+ * @param {sfra.Request} req - request object
+ * @returns {undefined}
+ */
+function updateCurrentBasket(currentBasket, req) {
+ const { details } = JSON.parse(req.form.data);
+ if (currentBasket.shipments?.length <= 1) {
+ req.session.privacyCache.set('usingMultiShipping', false);
+ }
+
+ paypalHelper.setBillingAndShippingAddress(currentBasket);
+
+ const paymentInstrument = currentBasket.getPaymentInstruments()[0];
+ Transaction.wrap(() => {
+ paymentInstrument.custom.adyenPaymentMethod =
+ AdyenHelper.getAdyenComponentType(details?.paymentSource);
+ });
+}
+
+/**
+ * Controller for the checkout review page for express payment methods
+ * @param {sfra.Request} req - request
+ * @param {sfra.Response} res - response
+ * @param {sfra.Next} next - next
+ * @returns {sfra.Next} next - next
+ */
+function handleCheckoutReview(req, res, next) {
+ try {
+ if (!req.form.data) {
+ throw new Error('State data not present in the request');
+ }
+ const currentBasket = BasketMgr.getCurrentBasket();
+ if (!currentBasket) {
+ res.redirect(URLUtils.url('Cart-Show'));
+ return next();
+ }
+
+ const validatedProducts = validationHelpers.validateProducts(currentBasket);
+ if (validatedProducts.error) {
+ res.redirect(URLUtils.url('Cart-Show'));
+ return next();
+ }
+
+ updateCurrentBasket(currentBasket, req);
+
+ const currentCustomer = req.currentCustomer.raw;
+ const currentLocale = Locale.getLocale(req.locale.id);
+ const usingMultiShipping =
+ req.session.privacyCache.get('usingMultiShipping');
+
+ const orderModel = new OrderModel(currentBasket, {
+ customer: currentCustomer,
+ usingMultiShipping,
+ shippable: true,
+ countryCode: currentLocale.country,
+ containerView: 'basket',
+ });
+
+ const accountModel = new AccountModel(req.currentCustomer);
+
+ res.render('cart/checkoutReview', {
+ data: req.form.data,
+ showConfirmationUrl: URLUtils.https(
+ 'Adyen-ShowConfirmationPaymentFromComponent',
+ ),
+ order: orderModel,
+ customer: accountModel,
+ });
+ } catch (error) {
+ AdyenLogs.error_log('Could not render checkout review page', error);
+ res.redirect(URLUtils.url('Error-ErrorCode', 'err', 'general'));
+ }
+ return next();
+}
+
+module.exports = handleCheckoutReview;
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall.js
new file mode 100644
index 000000000..1c77030eb
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall.js
@@ -0,0 +1,69 @@
+const URLUtils = require('dw/web/URLUtils');
+const OrderMgr = require('dw/order/OrderMgr');
+const Transaction = require('dw/system/Transaction');
+const BasketMgr = require('dw/order/BasketMgr');
+const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+const COHelpers = require('*/cartridge/scripts/checkout/checkoutHelpers');
+const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
+const paypalHelper = require('*/cartridge/adyen/utils/paypalHelper');
+const constants = require('*/cartridge/adyen/config/constants');
+
+function setPaymentInstrumentFields(paymentInstrument, response) {
+ paymentInstrument.custom.adyenPaymentMethod =
+ AdyenHelper.getAdyenComponentType(response.paymentMethod.type);
+ paymentInstrument.custom[`${constants.OMS_NAMESPACE}__Adyen_Payment_Method`] =
+ AdyenHelper.getAdyenComponentType(response.paymentMethod.type);
+ paymentInstrument.custom.Adyen_Payment_Method_Variant =
+ response.paymentMethod.type.toLowerCase();
+ paymentInstrument.custom[
+ `${constants.OMS_NAMESPACE}__Adyen_Payment_Method_Variant`
+ ] = response.paymentMethod.type.toLowerCase();
+}
+
+/*
+ * Makes a payment details call to Adyen to confirm the current status of a payment.
+ It is currently used only for PayPal Express Flow
+ */
+function makeExpressPaymentDetailsCall(req, res, next) {
+ try {
+ const request = JSON.parse(req.body);
+ const currentBasket = BasketMgr.getCurrentBasket();
+
+ const response = adyenCheckout.doPaymentsDetailsCall(request.data);
+
+ paypalHelper.setBillingAndShippingAddress(currentBasket);
+
+ // Setting the session variable to null after assigning the shopper data to basket level
+ session.privacy.shopperDetails = null;
+
+ const order = OrderMgr.createOrder(
+ currentBasket,
+ session.privacy.paypalExpressOrderNo,
+ );
+ const fraudDetectionStatus = { status: 'success' };
+ const placeOrderResult = COHelpers.placeOrder(order, fraudDetectionStatus);
+ if (placeOrderResult.error) {
+ throw new Error('Failed to place the PayPal express order');
+ }
+
+ response.orderNo = order.orderNo;
+ response.orderToken = order.orderToken;
+ const paymentInstrument = order.getPaymentInstruments(
+ AdyenHelper.getOrderMainPaymentInstrumentType(order),
+ )[0];
+ // Storing the paypal express response to make use of show confirmation logic
+ Transaction.wrap(() => {
+ order.custom.Adyen_paypalExpressResponse = JSON.stringify(response);
+ setPaymentInstrumentFields(paymentInstrument, response);
+ });
+ res.json({ orderNo: response.orderNo, orderToken: response.orderToken });
+ return next();
+ } catch (error) {
+ AdyenLogs.error_log('Could not verify express /payment/details:', error);
+ res.redirect(URLUtils.url('Error-ErrorCode', 'err', 'general'));
+ return next();
+ }
+}
+
+module.exports = makeExpressPaymentDetailsCall;
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall.js
new file mode 100644
index 000000000..8065c7996
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall.js
@@ -0,0 +1,66 @@
+const BasketMgr = require('dw/order/BasketMgr');
+const PaymentMgr = require('dw/order/PaymentMgr');
+const OrderMgr = require('dw/order/OrderMgr');
+const Transaction = require('dw/system/Transaction');
+const Resource = require('dw/web/Resource');
+const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
+const constants = require('*/cartridge/adyen/config/constants');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
+const paypalHelper = require('*/cartridge/adyen/utils/paypalHelper');
+
+function makeExpressPaymentsCall(req, res, next) {
+ try {
+ const currentBasket = BasketMgr.getCurrentBasket();
+ let paymentInstrument;
+ Transaction.wrap(() => {
+ currentBasket.removeAllPaymentInstruments();
+ paymentInstrument = currentBasket.createPaymentInstrument(
+ constants.METHOD_ADYEN_COMPONENT,
+ currentBasket.getAdjustedMerchandizeTotalGrossPrice(),
+ );
+ const { paymentProcessor } = PaymentMgr.getPaymentMethod(
+ paymentInstrument.paymentMethod,
+ );
+ paymentInstrument.paymentTransaction.paymentProcessor = paymentProcessor;
+ paymentInstrument.custom.adyenPaymentData = req.body;
+ });
+ // creates order number to be utilized for PayPal express
+ const paypalExpressOrderNo = OrderMgr.createOrderNo();
+ // Create request object with payment details
+ const paymentRequest = AdyenHelper.createAdyenRequestObject(
+ paypalExpressOrderNo,
+ null,
+ paymentInstrument,
+ );
+ paymentRequest.amount = {
+ currency: paymentInstrument.paymentTransaction.amount.currencyCode,
+ value: AdyenHelper.getCurrencyValueForApi(
+ paymentInstrument.paymentTransaction.amount,
+ ).getValueOrNull(),
+ };
+ paymentRequest.lineItems = paypalHelper.getLineItems({
+ Basket: currentBasket,
+ });
+ let result;
+ Transaction.wrap(() => {
+ result = adyenCheckout.doPaymentsCall(
+ null,
+ paymentInstrument,
+ paymentRequest,
+ );
+ });
+ session.privacy.paypalExpressOrderNo = paypalExpressOrderNo;
+ session.privacy.pspReference = result.pspReference;
+ res.json(result);
+ } catch (error) {
+ AdyenLogs.fatal_log('Paypal express payments request failed', error);
+ res.setStatusCode(500);
+ res.json({
+ errorMessage: Resource.msg('error.express.paypal.payments', 'cart', null),
+ });
+ }
+ return next();
+}
+
+module.exports = makeExpressPaymentsCall;
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData.js
new file mode 100644
index 000000000..f2d3571d4
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData.js
@@ -0,0 +1,17 @@
+const URLUtils = require('dw/web/URLUtils');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+
+function saveShopperData(req, res, next) {
+ try {
+ const shopperDetails = JSON.parse(req.form.shopperDetails);
+ session.privacy.shopperDetails = JSON.stringify(shopperDetails);
+ res.json({ success: true });
+ return next();
+ } catch (error) {
+ AdyenLogs.error_log('Failed to save the shopper details:', error);
+ res.redirect(URLUtils.url('Error-ErrorCode', 'err', 'general'));
+ return next();
+ }
+}
+
+module.exports = saveShopperData;
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/saveExpressShopperDetails.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/saveExpressShopperDetails.js
index c53b0a9c2..123e99957 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/saveExpressShopperDetails.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/saveExpressShopperDetails.js
@@ -64,8 +64,10 @@ function saveExpressShopperDetails(req, res, next) {
JSON.stringify(shopperDetails);
});
setBillingAndShippingAddress(currentBasket);
- const shippingMethods = AdyenHelper.callGetShippingMethods(
- shopperDetails.shippingAddress,
+ const { shippingAddress } = currentBasket.getDefaultShipment();
+ const shippingMethods = AdyenHelper.getApplicableShippingMethods(
+ currentBasket.getDefaultShipment(),
+ shippingAddress,
);
res.json({ shippingMethods });
return next();
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/selectShippingMethods.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/selectShippingMethods.js
index ba5d3cfe9..2c197c57a 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/selectShippingMethods.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/selectShippingMethods.js
@@ -5,6 +5,11 @@ const URLUtils = require('dw/web/URLUtils');
const CartModel = require('*/cartridge/models/cart');
const shippingHelper = require('*/cartridge/scripts/checkout/shippingHelpers');
const basketCalculationHelpers = require('*/cartridge/scripts/helpers/basketCalculationHelpers');
+const { PAYMENTMETHODS } = require('*/cartridge/adyen/config/constants');
+const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
+const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
+const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
+const paypalHelper = require('*/cartridge/adyen/utils/paypalHelper');
/**
* Make a request to Adyen to select shipping methods
@@ -21,37 +26,56 @@ function callSelectShippingMethod(req, res, next) {
return next();
}
+ try {
+ const { shipmentUUID, methodID, currentPaymentData, paymentMethodType } =
+ JSON.parse(req.body);
+ let shipment;
+ if (shipmentUUID) {
+ shipment = shippingHelper.getShipmentByUUID(currentBasket, shipmentUUID);
+ } else {
+ shipment = currentBasket.defaultShipment;
+ }
- let error = false;
-
- const shipUUID = req.querystring.shipmentUUID || req.form.shipmentUUID;
- const methodID = req.querystring.methodID || req.form.methodID;
- let shipment;
- if (shipUUID) {
- shipment = shippingHelper.getShipmentByUUID(currentBasket, shipUUID);
- } else {
- shipment = currentBasket.defaultShipment;
- }
+ Transaction.wrap(() => {
+ shippingHelper.selectShippingMethod(shipment, methodID);
- Transaction.wrap(() => {
- shippingHelper.selectShippingMethod(shipment, methodID);
+ if (currentBasket && !shipment.shippingMethod) {
+ throw new Error(
+ `cannot set shippingMethod: ${methodID} for shipment:${shipment?.UUID}`,
+ );
+ }
- if (currentBasket && !shipment.shippingMethod) {
- error = true;
- return;
+ basketCalculationHelpers.calculateTotals(currentBasket);
+ });
+ let response = {};
+ if (paymentMethodType === PAYMENTMETHODS.PAYPAL) {
+ const currentShippingMethodsModels =
+ AdyenHelper.getApplicableShippingMethods(shipment);
+ if (!currentShippingMethodsModels?.length) {
+ throw new Error('No applicable shipping methods found');
+ }
+ const paypalUpdateOrderResponse = adyenCheckout.doPaypalUpdateOrderCall(
+ paypalHelper.createPaypalUpdateOrderRequest(
+ session.privacy.pspReference,
+ currentBasket,
+ currentShippingMethodsModels,
+ currentPaymentData,
+ ),
+ );
+ AdyenLogs.info_log(
+ `Paypal Order Update Call: ${paypalUpdateOrderResponse.status}`,
+ );
+ response = { ...response, ...paypalUpdateOrderResponse };
}
-
- basketCalculationHelpers.calculateTotals(currentBasket);
- });
-
- if (!error) {
const basketModel = new CartModel(currentBasket);
const grandTotalAmount = {
value: currentBasket.getTotalGrossPrice().value,
currency: currentBasket.getTotalGrossPrice().currencyCode,
};
- res.json({ ...basketModel, grandTotalAmount });
- } else {
+ response = { ...response, ...basketModel, grandTotalAmount };
+ res.json(response);
+ } catch (error) {
+ AdyenLogs.error_log('Failed to set shipping method', error);
res.setStatusCode(500);
res.json({
errorMessage: Resource.msg(
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/shippingMethods.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/shippingMethods.js
index b9e95ad76..186928bbf 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/shippingMethods.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/shippingMethods.js
@@ -1,65 +1,84 @@
const BasketMgr = require('dw/order/BasketMgr');
const Transaction = require('dw/system/Transaction');
+const Resource = require('dw/web/Resource');
+const URLUtils = require('dw/web/URLUtils');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
+const { PAYMENTMETHODS } = require('*/cartridge/adyen/config/constants');
+const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
+const paypalHelper = require('*/cartridge/adyen/utils/paypalHelper');
-const addressMapping = {
- city: 'setCity',
- countryCode: 'setCountryCode',
- stateCode: 'setStateCode',
- postalCode: 'setPostalCode',
-};
-
-/**
- * Sets address properties for express PM
- * @param {dw.order.shippingAddress} shippingAddress - shippingAddress for the default shipment
- * @param {object} inputAddress - address coming from the input field based on shopper selection
- * @param {object} mapping - address mapping between property and setter for that property
- */
-function setAddressProperties(shippingAddress, inputAddress, mapping) {
- Object.keys(inputAddress).forEach((key) => {
- if (inputAddress[key] && mapping[key]) {
- shippingAddress[mapping[key]](inputAddress[key]);
- }
- });
-}
-
-/**
- * Make a request to Adyen to get shipping methods
- */
-function callGetShippingMethods(req, res, next) {
- try {
- let address = null;
- if (req.querystring) {
- address = {
- city: req.querystring.city,
- countryCode: req.querystring.countryCode,
- stateCode: req.querystring.stateCode,
- postalCode: req.querystring.postalCode,
- };
- }
- const currentBasket = BasketMgr.getCurrentBasket();
- const shipment = currentBasket.getDefaultShipment();
+function updateShippingAddress(currentBasket, address) {
+ if (address) {
+ let { shippingAddress } = currentBasket.getDefaultShipment();
Transaction.wrap(() => {
- let { shippingAddress } = shipment;
if (!shippingAddress) {
shippingAddress = currentBasket
.getDefaultShipment()
.createShippingAddress();
}
- if (address) {
- setAddressProperties(shippingAddress, address, addressMapping);
- }
+ shippingAddress.setCity(address?.city);
+ shippingAddress.setPostalCode(address?.postalCode);
+ shippingAddress.setStateCode(address?.stateCode);
+ shippingAddress.setCountryCode(address?.countryCode);
});
+ }
+}
+/**
+ * Make a request to Adyen to get shipping methods
+ */
+function callGetShippingMethods(req, res, next) {
+ try {
+ const { address, currentPaymentData, paymentMethodType } = JSON.parse(
+ req.body,
+ );
+ const currentBasket = BasketMgr.getCurrentBasket();
+ if (!currentBasket) {
+ res.json({
+ error: true,
+ redirectUrl: URLUtils.url('Cart-Show').toString(),
+ });
+
+ return next();
+ }
+ updateShippingAddress(currentBasket, address);
+ currentBasket.updateTotals();
const currentShippingMethodsModels =
- AdyenHelper.getApplicableShippingMethods(shipment, address);
- res.json({
- shippingMethods: currentShippingMethodsModels,
- });
+ AdyenHelper.getApplicableShippingMethods(
+ currentBasket.getDefaultShipment(),
+ address,
+ );
+ if (!currentShippingMethodsModels?.length) {
+ throw new Error('No applicable shipping methods found');
+ }
+ let response = {};
+ if (paymentMethodType === PAYMENTMETHODS.PAYPAL) {
+ const paypalUpdateOrderResponse = adyenCheckout.doPaypalUpdateOrderCall(
+ paypalHelper.createPaypalUpdateOrderRequest(
+ session.privacy.pspReference,
+ currentBasket,
+ currentShippingMethodsModels,
+ currentPaymentData,
+ ),
+ );
+ AdyenLogs.info_log(
+ `Paypal Order Update Call: ${paypalUpdateOrderResponse.status}`,
+ );
+ response = { ...response, ...paypalUpdateOrderResponse };
+ }
+ response.shippingMethods = currentShippingMethodsModels;
+ res.json(response);
return next();
} catch (error) {
- AdyenLogs.error_log('Failed to fetch shipping methods');
- AdyenLogs.error_log(error);
+ AdyenLogs.error_log('Failed to fetch shipping methods', error);
+ res.setStatusCode(500);
+ res.json({
+ errorMessage: Resource.msg(
+ 'error.cannot.find.shipping.methods',
+ 'cart',
+ null,
+ ),
+ });
return next();
}
}
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/index.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/index.js
index b34ff4b77..be9abcdc4 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/index.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/index.js
@@ -13,6 +13,10 @@ const fetchGiftCards = require('*/cartridge/adyen/scripts/partialPayments/fetchG
const showConfirmationPaymentFromComponent = require('*/cartridge/adyen/scripts/showConfirmation/showConfirmationPaymentFromComponent');
const showConfirmation = require('*/cartridge/adyen/scripts/showConfirmation/showConfirmation');
const notify = require('*/cartridge/adyen/webhooks/notify');
+const makeExpressPaymentsCall = require('*/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall');
+const makeExpressPaymentDetailsCall = require('*/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall');
+const saveShopperData = require('*/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData');
+const handleCheckoutReview = require('*/cartridge/adyen/scripts/expressPayments/paypal/handleCheckoutReview');
module.exports = {
getCheckoutPaymentMethods,
@@ -30,4 +34,8 @@ module.exports = {
showConfirmation,
showConfirmationPaymentFromComponent,
notify,
+ makeExpressPaymentsCall,
+ makeExpressPaymentDetailsCall,
+ saveShopperData,
+ handleCheckoutReview,
};
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/adyenCheckout.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/adyenCheckout.test.js
index 044d1b2b9..e879d11a4 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/adyenCheckout.test.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/adyenCheckout.test.js
@@ -8,12 +8,14 @@ describe('AdyenCheckout', () => {
custom: {},
setPaymentStatus: jest.fn(),
setExportStatus: jest.fn(),
+ getOrderNo: jest.fn(),
+ getOrderToken: jest.fn(),
},
PaymentInstrument: {
custom: {
adyenPaymentData: "{}",
adyenPartialPaymentsOrder:
- '{"orderData":"b4c0!BQABAgBzO7ZwfyxJ9ifN0NIgUsuwBdUWb==...",' +
+ '{"orderData":"b4c0!BQABAgBzO7ZwfyxJ9ifN0NIgUsuwBdUWb==...",' +
'"remainingAmount":{"currency":"EUR","value":20799},' +
'"amount":{"currency":"EUR","value":1000}}'
@@ -39,12 +41,14 @@ describe('AdyenCheckout', () => {
custom: {},
setPaymentStatus: jest.fn(),
setExportStatus: jest.fn(),
+ getOrderNo: jest.fn(),
+ getOrderToken: jest.fn(),
},
PaymentInstrument: {
custom: {
adyenPaymentData: "{}",
adyenPartialPaymentsOrder:
- '{"orderData":"b4c0!BQABAgBzO7ZwfyxJ9ifN0NIgUsuwBdUWb==...",' +
+ '{"orderData":"b4c0!BQABAgBzO7ZwfyxJ9ifN0NIgUsuwBdUWb==...",' +
'"remainingAmount":{"currency":"EUR","value":20799},' +
'"amount":{"currency":"EUR","value":25799}}'
@@ -70,12 +74,14 @@ describe('AdyenCheckout', () => {
custom: {},
setPaymentStatus: jest.fn(),
setExportStatus: jest.fn(),
+ getOrderNo: jest.fn(),
+ getOrderToken: jest.fn(),
},
PaymentInstrument: {
custom: {
adyenPaymentData: "{}",
adyenPartialPaymentsOrder:
- '{"orderData":"b4c0!BQABAgBzO7ZwfyxJ9ifN0NIgUsuwBdUWb==...",' +
+ '{"orderData":"b4c0!BQABAgBzO7ZwfyxJ9ifN0NIgUsuwBdUWb==...",' +
'"remainingAmount":{"currency":"USD","value":20799},' +
'"amount":{"currency":"USD","value":1000}}'
@@ -93,4 +99,4 @@ describe('AdyenCheckout', () => {
expect(Logger.error.mock.calls[0][0]).toContain("Cart has been edited after applying a gift card");
expect(response.error).toEqual(true);
})
-})
\ No newline at end of file
+})
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/getCheckoutPaymentMethods.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/getCheckoutPaymentMethods.test.js
index 84172cd88..0f234d3d5 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/getCheckoutPaymentMethods.test.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/getCheckoutPaymentMethods.test.js
@@ -1,4 +1,5 @@
/* eslint-disable global-require */
+const BasketMgr = require('dw/order/BasketMgr');
const getCheckoutPaymentMethods = require('*/cartridge/adyen/scripts/payments/getCheckoutPaymentMethods');
const getPaymentMethods = require('*/cartridge/adyen/scripts/payments/adyenGetPaymentMethods');
let req;
@@ -24,6 +25,25 @@ afterEach(() => {
describe('getCheckoutPaymentMethods', () => {
it('returns AdyenPaymentMethods', () => {
+ currentBasket = {
+ getDefaultShipment: jest.fn(() => {
+ return {
+ shippingAddress: {
+ getCountryCode: jest.fn(() => {
+ return {
+ value: "NL"
+ }
+ })
+ }}
+ }),
+ getTotalGrossPrice: jest.fn(() => {
+ return {
+ currencyCode: 'EUR',
+ value: '1000'
+ }
+ })
+ };
+ BasketMgr.getCurrentBasket.mockReturnValueOnce(currentBasket);
getCheckoutPaymentMethods(req, res, next);
expect(res.json).toHaveBeenCalledWith({
AdyenPaymentMethods: {
@@ -62,7 +82,7 @@ describe('getCheckoutPaymentMethods', () => {
getCheckoutPaymentMethods(req, res, next);
expect(res.json).toHaveBeenCalledWith({
error: true,
- });
+ });
expect(Logger.fatal.mock.calls.length).toBe(1);
expect(next).toHaveBeenCalled();
});
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paymentsDetails.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paymentsDetails.test.js
index b0b0323b5..60d335c87 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paymentsDetails.test.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paymentsDetails.test.js
@@ -62,21 +62,18 @@ describe('Confirm paymentsDetails', () => {
it('should call paymentDetails request and response handler', () => {
const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
- const URLUtils = require('dw/web/URLUtils');
-
adyenCheckout.doPaymentsDetailsCall.mockImplementation(() => ({
resultCode:'mocked_resultCode',
pspReference: 'mocked_pspReference',
}));
paymentsDetails(req, res, jest.fn());
- expect(URLUtils.url.mock.calls[0][0]).toEqual('Adyen-ShowConfirmation');
+ expect(AdyenHelper.createRedirectUrl.mock.calls.length).toEqual(1);
expect(adyenCheckout.doPaymentsDetailsCall.mock.calls.length).toEqual(1);
expect(AdyenHelper.createAdyenCheckoutResponse.mock.calls.length).toEqual(1);
-
expect(res.json.mock.calls[0][0]).toEqual({
isFinal: true,
isSuccessful: false,
- redirectUrl: "[\"Adyen-ShowConfirmation\",\"merchantReference\",null,\"signature\",\"mocked_signature\",\"orderToken\",null]"
+ redirectUrl: "mocked_RedirectUrl"
});
});
});
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paypalHelper.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paypalHelper.test.js
deleted file mode 100644
index 98c2e0bcc..000000000
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/__tests__/paypalHelper.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-const paypalHelper = require('../paypalHelper')
-describe('paypalHelper', () => {
- let args,lineItem, result
- beforeEach(() => {
- args = (item) => ({
- Order: {
- getAllLineItems: jest.fn(() => ([item]))
- }
- })
-
- lineItem = {
- productName: 'test',
- productID: '123',
- quantityValue: '1',
- getAdjustedTax: '1000',
- adjustedNetPrice: '10000',
- category: 'PHYSICAL_GOODS',
- }
-
- result = {
- quantity: '1',
- description: 'test',
- itemCategory: 'PHYSICAL_GOODS',
- sku: '123',
- amountExcludingTax: '10000',
- taxAmount: '1000'
- }
- })
- it('should return lineItems for paypal', () => {
-const paypalLineItems = paypalHelper.getLineItems(args(lineItem))
- expect(paypalLineItems[0]).toStrictEqual(result)
- })
-
- it('should return lineItems for paypal with default itemCategory when category is not as per paypal', () => {
- const paypalLineItems = paypalHelper.getLineItems(args({...lineItem, category: 'TEST_GOODS'}))
- expect(paypalLineItems[0]).toStrictEqual({
- quantity: '1',
- description: 'test',
- sku: '123',
- amountExcludingTax: '10000',
- taxAmount: '1000'
- })
- })
-
- it('should return no lineItems for paypal if order or basket is not defined', () => {
-
- const paypalLineItems = paypalHelper.getLineItems({})
- expect(paypalLineItems).toBeNull()
- })
-})
\ No newline at end of file
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenCheckout.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenCheckout.js
index d9f9e36f8..ac2763f83 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenCheckout.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenCheckout.js
@@ -26,7 +26,6 @@
const Resource = require('dw/web/Resource');
const Order = require('dw/order/Order');
const StringUtils = require('dw/util/StringUtils');
-
/* Script Modules */
const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
const AdyenConfigs = require('*/cartridge/adyen/utils/adyenConfigs');
@@ -35,7 +34,7 @@ const AdyenGetOpenInvoiceData = require('*/cartridge/adyen/scripts/payments/adye
const adyenLevelTwoThreeData = require('*/cartridge/adyen/scripts/payments/adyenLevelTwoThreeData');
const constants = require('*/cartridge/adyen/config/constants');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
-const paypalHelper = require('*/cartridge/adyen/scripts/payments/paypalHelper');
+const paypalHelper = require('*/cartridge/adyen/utils/paypalHelper');
// eslint-disable-next-line complexity
function doPaymentsCall(order, paymentInstrument, paymentRequest) {
@@ -144,13 +143,13 @@ function createPaymentRequest(args) {
// Create request object with payment details
let paymentRequest = AdyenHelper.createAdyenRequestObject(
- order,
+ order.getOrderNo(),
+ order.getOrderToken(),
paymentInstrument,
);
- paymentRequest = AdyenHelper.add3DS2Data(paymentRequest);
const paymentMethodType = paymentRequest.paymentMethod.type;
-
+ paymentRequest = AdyenHelper.add3DS2Data(paymentRequest);
// Add Risk data
if (AdyenConfigs.getAdyenBasketFieldsEnabled()) {
paymentRequest.additionalData =
@@ -180,7 +179,6 @@ function createPaymentRequest(args) {
paymentRequest.installments = { value: numOfInstallments };
}
}
-
const value = AdyenHelper.getCurrencyValueForApi(
paymentInstrument.paymentTransaction.amount,
).getValueOrNull();
@@ -213,6 +211,7 @@ function createPaymentRequest(args) {
paymentMethodType,
paymentRequest,
);
+
// Create shopper data fields
paymentRequest = AdyenHelper.createShopperObject({
order,
@@ -268,7 +267,6 @@ function createPaymentRequest(args) {
paymentInstrument,
paymentRequest.paymentMethod,
);
- // make API call
return doPaymentsCall(order, paymentInstrument, paymentRequest);
} catch (e) {
AdyenLogs.error_log(
@@ -313,6 +311,13 @@ function doCreatePartialPaymentOrderCall(partialPaymentRequest) {
);
}
+function doPaypalUpdateOrderCall(paypalUpdateOrderRequest) {
+ return AdyenHelper.executeCall(
+ constants.SERVICE.PAYPALUPDATEORDER,
+ paypalUpdateOrderRequest,
+ );
+}
+
module.exports = {
createPaymentRequest,
doPaymentsCall,
@@ -320,4 +325,5 @@ module.exports = {
doCheckBalanceCall,
doCancelPartialPaymentOrderCall,
doCreatePartialPaymentOrderCall,
+ doPaypalUpdateOrderCall,
};
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenZeroAuth.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenZeroAuth.js
index 86dcee5ec..e1d101361 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenZeroAuth.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenZeroAuth.js
@@ -31,7 +31,8 @@ const constants = require('*/cartridge/adyen/config/constants');
function zeroAuthPayment(customer, paymentInstrument) {
try {
let zeroAuthRequest = AdyenHelper.createAdyenRequestObject(
- null,
+ 'recurringPayment-account',
+ 'recurringPayment-token',
paymentInstrument,
);
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/getCheckoutPaymentMethods.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/getCheckoutPaymentMethods.js
index 958df8a5a..e218b8f73 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/getCheckoutPaymentMethods.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/getCheckoutPaymentMethods.js
@@ -1,6 +1,7 @@
const BasketMgr = require('dw/order/BasketMgr');
const Locale = require('dw/util/Locale');
const PaymentMgr = require('dw/order/PaymentMgr');
+const URLUtils = require('dw/web/URLUtils');
const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
const adyenTerminalApi = require('*/cartridge/adyen/scripts/payments/adyenTerminalApi');
const paymentMethodDescriptions = require('*/cartridge/adyen/config/paymentMethodDescriptions');
@@ -9,12 +10,12 @@ const getPaymentMethods = require('*/cartridge/adyen/scripts/payments/adyenGetPa
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
function getCountryCode(currentBasket, locale) {
- const countryCode = Locale.getLocale(locale.id).country;
- const firstItem = currentBasket?.getShipments()?.[0];
- if (firstItem?.shippingAddress) {
- return firstItem.shippingAddress.getCountryCode().value;
+ let countryCode;
+ const { shippingAddress } = currentBasket.getDefaultShipment();
+ if (shippingAddress) {
+ countryCode = shippingAddress.getCountryCode().value;
}
- return countryCode;
+ return countryCode || Locale.getLocale(locale.id).country;
}
function getConnectedTerminals() {
@@ -27,11 +28,15 @@ function getConnectedTerminals() {
function getCheckoutPaymentMethods(req, res, next) {
try {
const currentBasket = BasketMgr.getCurrentBasket();
- const countryCode =
- currentBasket.getShipments().length > 0 &&
- currentBasket.getShipments()[0].shippingAddress
- ? currentBasket.getShipments()[0].shippingAddress.getCountryCode().value
- : getCountryCode(currentBasket, req.locale).value;
+ if (!currentBasket) {
+ res.json({
+ error: true,
+ redirectUrl: URLUtils.url('Cart-Show').toString(),
+ });
+
+ return next();
+ }
+ const countryCode = getCountryCode(currentBasket, req.locale);
const adyenURL = `${AdyenHelper.getLoadingContext()}images/logos/medium/`;
const connectedTerminals = JSON.parse(getConnectedTerminals());
const currency = currentBasket.getTotalGrossPrice().currencyCode;
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paymentsDetails.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paymentsDetails.js
index 80077c651..b0052d14a 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paymentsDetails.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paymentsDetails.js
@@ -5,7 +5,7 @@ const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
-function getSignature(paymentsDetailsResponse, orderToken) {
+function getRedirectUrl(paymentsDetailsResponse, orderToken) {
const order = OrderMgr.getOrder(
paymentsDetailsResponse.merchantReference,
orderToken,
@@ -14,18 +14,16 @@ function getSignature(paymentsDetailsResponse, orderToken) {
const paymentInstruments = order.getPaymentInstruments(
AdyenHelper.getOrderMainPaymentInstrumentType(order),
);
-
- const signature = AdyenHelper.createSignature(
+ const redirectUrl = AdyenHelper.createRedirectUrl(
paymentInstruments[0],
- order.getUUID(),
paymentsDetailsResponse.merchantReference,
+ orderToken,
);
-
Transaction.wrap(() => {
paymentInstruments[0].paymentTransaction.custom.Adyen_authResult =
JSON.stringify(paymentsDetailsResponse);
});
- return signature;
+ return redirectUrl;
}
return undefined;
}
@@ -49,10 +47,11 @@ function paymentsDetails(req, res, next) {
const response = AdyenHelper.createAdyenCheckoutResponse(
paymentsDetailsResponse,
);
-
// Create signature to verify returnUrl
- const signature = getSignature(paymentsDetailsResponse, request.orderToken);
-
+ const redirectUrl = getRedirectUrl(
+ paymentsDetailsResponse,
+ request.orderToken,
+ );
if (isAmazonpay) {
response.fullResponse = {
pspReference: paymentsDetailsResponse.pspReference,
@@ -60,16 +59,8 @@ function paymentsDetails(req, res, next) {
resultCode: paymentsDetailsResponse.resultCode,
};
}
- if (signature !== null) {
- response.redirectUrl = URLUtils.https(
- 'Adyen-ShowConfirmation',
- 'merchantReference',
- response.merchantReference,
- 'signature',
- signature,
- 'orderToken',
- request.orderToken,
- ).toString();
+ if (redirectUrl) {
+ response.redirectUrl = redirectUrl;
}
res.json(response);
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paypalHelper.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paypalHelper.js
deleted file mode 100644
index 4fef811e2..000000000
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/paypalHelper.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * ######
- * ######
- * ############ ####( ###### #####. ###### ############ ############
- * ############# #####( ###### #####. ###### ############# #############
- * ###### #####( ###### #####. ###### ##### ###### ##### ######
- * ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
- * ###### ###### #####( ###### #####. ###### ##### ##### ######
- * ############# ############# ############# ############# ##### ######
- * ############ ############ ############# ############ ##### ######
- * ######
- * #############
- * ############
- * Adyen Salesforce Commerce Cloud
- * Copyright (c) 2021 Adyen B.V.
- * This file is open source and available under the MIT license.
- * See the LICENSE file for more info.
- *
- * Add all product and shipping line items to request
- */
-
-const LineItemHelper = require('*/cartridge/adyen/utils/lineItemHelper');
-
-const PAYPAL_ITEM_CATEGORY = ['PHYSICAL_GOODS', 'DIGITAL_GOODS', 'DONATION'];
-function getLineItems({ Order: order, Basket: basket }) {
- if (!(order || basket)) return null;
- const orderOrBasket = order || basket;
- const allLineItems = LineItemHelper.getAllLineItems(
- orderOrBasket.getAllLineItems(),
- );
- return allLineItems.map((lineItem) => {
- const lineItemObject = {};
- const description = LineItemHelper.getDescription(lineItem);
- const id = LineItemHelper.getId(lineItem);
- const quantity = LineItemHelper.getQuantity(lineItem);
- const itemAmount = LineItemHelper.getItemAmount(lineItem).divide(quantity);
- const vatAmount = LineItemHelper.getVatAmount(lineItem).divide(quantity);
- // eslint-disable-next-line
- if (lineItem.hasOwnProperty('category')) {
- if (PAYPAL_ITEM_CATEGORY.indexOf(lineItem.category) > -1) {
- lineItemObject.itemCategory = lineItem.category;
- }
- }
- lineItemObject.quantity = quantity;
- lineItemObject.description = description;
- lineItemObject.sku = id;
- lineItemObject.amountExcludingTax = itemAmount.getValue().toFixed();
- lineItemObject.taxAmount = vatAmount.getValue().toFixed();
- return lineItemObject;
- });
-}
-
-module.exports.getLineItems = getLineItems;
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js
index 30785110a..51bd060ab 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js
@@ -49,11 +49,14 @@ function handleAuthorisedPayment(
) {
// custom fraudDetection
const fraudDetectionStatus = { status: 'success' };
+ const isPayPalExpress = order.custom.Adyen_paypalExpressResponse;
- // Places the order
- const placeOrderResult = COHelpers.placeOrder(order, fraudDetectionStatus);
- if (placeOrderResult.error) {
- return handlePaymentError(order, adyenPaymentInstrument, { res, next });
+ // Places the order, for PayPal express the order is placed from makeExpressPaymentDetailsCall.js
+ if (!isPayPalExpress) {
+ const placeOrderResult = COHelpers.placeOrder(order, fraudDetectionStatus);
+ if (placeOrderResult.error) {
+ return handlePaymentError(order, adyenPaymentInstrument, { res, next });
+ }
}
Transaction.wrap(() => {
@@ -101,6 +104,10 @@ function handlePaymentResult(result, order, adyenPaymentInstrument, options) {
Transaction.wrap(() => {
order.custom.Adyen_pspReference = result.pspReference;
order.custom.Adyen_eventCode = result.resultCode;
+ order.custom.Adyen_paypalExpressResponse = null;
+ adyenPaymentInstrument.custom.adyenPaymentData = null;
+ session.privacy.paypalExpressOrderNo = null;
+ session.privacy.pspReference = null;
});
return handlePaymentError(order, adyenPaymentInstrument, options);
}
@@ -135,15 +142,21 @@ function handlePayment(stateData, order, options) {
return handlePaymentError(order, adyenPaymentInstrument, options);
}
}
-
- const detailsCall = hasStateData
- ? handlePaymentsDetailsCall(stateData, adyenPaymentInstrument)
- : null;
-
- Transaction.wrap(() => {
- adyenPaymentInstrument.custom.adyenPaymentData = null;
- });
- finalResult = finalResult || detailsCall?.result;
+ const paymentData = JSON.parse(
+ adyenPaymentInstrument.custom.adyenPaymentData,
+ );
+ const isPayPalExpress = AdyenHelper.isPayPalExpress(
+ paymentData.paymentMethod,
+ );
+ const detailsCall =
+ hasStateData && !isPayPalExpress
+ ? handlePaymentsDetailsCall(stateData, adyenPaymentInstrument)
+ : null;
+ if (isPayPalExpress) {
+ finalResult = JSON.parse(order.custom.Adyen_paypalExpressResponse);
+ } else {
+ finalResult = finalResult || detailsCall?.result;
+ }
return handlePaymentResult(
finalResult,
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/adyenHelper.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/adyenHelper.test.js
index e178dd8c6..4ecc6f9ce 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/adyenHelper.test.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/adyenHelper.test.js
@@ -1,4 +1,6 @@
/* eslint-disable global-require */
+const Money = require('../../../../../../../jest/__mocks__/dw/value/Money');
+const { getApplicableShippingMethods } = require('../adyenHelper');
const savePaymentDetails = require('../adyenHelper').savePaymentDetails;
describe('savePaymentDetails', () => {
let paymentInstrument;
@@ -67,3 +69,50 @@ describe('savePaymentDetails', () => {
expect(paymentInstrument.paymentTransaction.custom.Adyen_donationToken).toBe('donation-token-123');
});
});
+
+describe('getApplicableShippingMethods', () => {
+ let shippingMethod, shipment, address;
+ beforeEach(() => {
+ shippingMethod = {
+ description: 'Order received within 7-10 business days',
+ displayName: 'Ground',
+ ID: '001',
+ custom: {
+ estimatedArrivalTime: '7-10 Business Days'
+ },
+ getTaxClassID: jest.fn(),
+ };
+ shipment = {
+ UUID: 'mock_UUID',
+ shippingAddress: {
+ setCity: jest.fn(),
+ setPostalCode: jest.fn(),
+ setStateCode: jest.fn(),
+ setCountryCode: jest.fn(),
+ },
+ getProductLineItems: jest.fn(() => ({
+ toArray: jest.fn(() =>[{
+ getProduct: jest.fn(() => ({
+ getPriceModel: jest.fn(() => ({
+ getPrice: jest.fn(() => Money())
+ }))
+ })),
+ getQuantity: jest.fn()
+ }])
+ }))
+ };
+ address = {}
+ });
+ it('should return applicable shipping methods for shipment and address', () => {
+ const shippingMethods = getApplicableShippingMethods(shipment, address);
+ expect(shippingMethods).toStrictEqual([{"shipmentUUID": "mock_UUID", "shippingCost": {"currencyCode": "USD", "value": "10.99"}}, {"shipmentUUID": "mock_UUID", "shippingCost": {"currencyCode": "USD", "value": "10.99"}}]);
+ })
+ it('should return applicable shipping methods when address is not provided', () => {
+ const shippingMethods = getApplicableShippingMethods(shipment);
+ expect(shippingMethods).toStrictEqual([{"shipmentUUID": "mock_UUID", "shippingCost": {"currencyCode": "USD", "value": "10.99"}}, {"shipmentUUID": "mock_UUID", "shippingCost": {"currencyCode": "USD", "value": "10.99"}}]);
+ })
+ it('should return no shipping methods when shipment is not provided', () => {
+ const shippingMethods = getApplicableShippingMethods();
+ expect(shippingMethods).toBeNull();
+ })
+})
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/paypalHelper.test.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/paypalHelper.test.js
new file mode 100644
index 000000000..e64d03a1f
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/__tests__/paypalHelper.test.js
@@ -0,0 +1,186 @@
+/* eslint-disable global-require */
+const BasketMgr = require('dw/order/BasketMgr');
+jest.mock('dw/value/Money', () => jest.fn());
+jest.mock('*/cartridge/adyen/utils/adyenHelper', () => {
+ return {
+ getCurrencyValueForApi: jest.fn(() => {
+ return {
+ value: 1000
+ }
+ })
+ }
+})
+
+const paypalHelper = require('../paypalHelper')
+const Money = require('dw/value/Money');
+const { createBillingAddress } = require("../../../../../../../jest/__mocks__/dw/order/BasketMgr");
+describe('paypalHelper', () => {
+ describe('getLineItems', () => {
+ let args,lineItem, result
+ beforeEach(() => {
+ jest.clearAllMocks();
+ args = (item) => ({
+ Order: {
+ getAllLineItems: jest.fn(() => ([item]))
+ }
+ })
+
+ lineItem = {
+ productName: 'test',
+ productID: '123',
+ quantityValue: '1',
+ getAdjustedTax: '1000',
+ adjustedNetPrice: '10000',
+ category: 'PHYSICAL_GOODS',
+ }
+
+ result = {
+ quantity: '1',
+ description: 'test',
+ itemCategory: 'PHYSICAL_GOODS',
+ sku: '123',
+ amountExcludingTax: '10000',
+ taxAmount: '1000'
+ }
+ })
+
+ afterEach(() => {
+ jest.resetModules();
+ });
+
+ it('should return lineItems for paypal', () => {
+ const paypalLineItems = paypalHelper.getLineItems(args(lineItem))
+ expect(paypalLineItems[0]).toStrictEqual(result)
+ })
+
+ it('should return lineItems for paypal with default itemCategory when category is not as per paypal', () => {
+ const paypalLineItems = paypalHelper.getLineItems(args({...lineItem, category: 'TEST_GOODS'}))
+ expect(paypalLineItems[0]).toStrictEqual({
+ quantity: '1',
+ description: 'test',
+ sku: '123',
+ amountExcludingTax: '10000',
+ taxAmount: '1000'
+ })
+ })
+
+ it('should return no lineItems for paypal if order or basket is not defined', () => {
+
+ const paypalLineItems = paypalHelper.getLineItems({})
+ expect(paypalLineItems).toBeNull()
+ })
+ })
+ describe('createPaypalUpdateOrderRequest', () => {
+ let pspReference, currentBasket, currentShippingMethods, paymentData, result;
+ beforeEach(() => {
+ jest.clearAllMocks();
+ pspReference = 'test';
+ paymentData = 'test';
+ currentShippingMethods = [{
+ ID: '001',
+ displayName: 'test',
+ shippingCost: {
+ currencyCode: 'USD',
+ value: '10.00',
+ },
+ selected: true,
+ }]
+ currentBasket = {
+ currencyCode: 'USD',
+ getAdjustedShippingTotalGrossPrice: jest.fn(),
+ getAdjustedMerchandizeTotalGrossPrice: jest.fn(),
+ }
+
+ result = {
+ pspReference: 'test',
+ paymentData: 'test',
+ amount: {
+ currency: 'USD',
+ value: 2000
+ },
+ deliveryMethods:[{
+ reference: '001',
+ description: 'test',
+ type: 'Shipping',
+ amount: {
+ currency: 'USD',
+ value: 1000,
+ },
+ selected: true,
+ }],
+ }
+
+ })
+
+ it('should return UpdateOrderRequest object for paypal', () => {
+ Money.mockReturnValue(() => {return {value: 10, currency: 'TEST'}})
+ const paypalUpdateOrderRequest = paypalHelper.createPaypalUpdateOrderRequest(pspReference, currentBasket, currentShippingMethods, paymentData)
+ expect(paypalUpdateOrderRequest).toStrictEqual(result)
+ })
+ })
+ describe('setBillingAndShippingAddress', () => {
+ let shopperDetails, billingAddress, shippingAddress;
+ beforeEach(() => {
+ jest.clearAllMocks();
+ billingAddress = require('../../../../../../../jest/__mocks__/dw/order/BasketMgr');
+ shopperDetails = {
+ shopperName:{
+ firstName: 'John',
+ lastName: 'Doe'
+ },
+ billingAddress:{
+ street: '123 Main St',
+ city: 'City',
+ postalCode: '12345',
+ stateOrProvince: 'State',
+ country: 'United States',
+ },
+ shippingAddress:{
+ street: '123 Main St',
+ city: 'City',
+ postalCode: '12345',
+ stateOrProvince: 'State',
+ country: 'United States',
+ },
+ telephoneNumber: '+1234567890',
+ shopperEmail: 'john@example.com'
+ }
+ session.privacy.shopperDetails = JSON.stringify(shopperDetails);
+ });
+ afterEach(() => {
+ jest.resetModules();
+ });
+ it('should update billing and shipping address for current basket', () => {
+ const currentBasket = BasketMgr.getCurrentBasket();
+ paypalHelper.setBillingAndShippingAddress(currentBasket);
+ expect(currentBasket.billingAddress.setFirstName).toHaveBeenCalledWith('John');
+ })
+ it('should set billing and shipping address if current basket has no billing Address', () => {
+ const currentBasket = BasketMgr.getCurrentBasket();
+ currentBasket.billingAddress= '';
+ paypalHelper.setBillingAndShippingAddress(currentBasket);
+ expect(currentBasket.createBillingAddress).toHaveBeenCalled();
+ })
+ it('should set billing and shipping address if current basket has no shipping Address', () => {
+ const currentBasket = BasketMgr.getCurrentBasket();
+ const createShippingAddress = jest.fn(() => ({
+ setPostalCode: jest.fn(),
+ setAddress1: jest.fn(),
+ setAddress2: jest.fn(),
+ setCountryCode: jest.fn(),
+ setCity: jest.fn(),
+ setFirstName: jest.fn(),
+ setLastName: jest.fn(),
+ setPhone: jest.fn(),
+ setStateCode: jest.fn(),
+ }));
+ currentBasket.getDefaultShipment= jest.fn(() => ({
+ createShippingAddress: createShippingAddress
+ }));
+ paypalHelper.setBillingAndShippingAddress(currentBasket);
+ expect(currentBasket.getDefaultShipment).toHaveBeenCalled();
+ expect(createShippingAddress).toHaveBeenCalled();
+ })
+ })
+})
+
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenConfigs.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenConfigs.js
index 2d42940c5..574935192 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenConfigs.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenConfigs.js
@@ -147,6 +147,14 @@ const adyenConfigsObj = {
return getCustomPreference('AmazonPayExpress_Enabled');
},
+ isPayPalExpressEnabled() {
+ return getCustomPreference('PayPalExpress_Enabled');
+ },
+
+ isPayPalExpressReviewPageEnabled() {
+ return getCustomPreference('PayPalExpress_ReviewPage_Enabled');
+ },
+
getExpressPaymentsOrder() {
return getCustomPreference('ExpressPayments_order');
},
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js
index 147af825b..844b7fc23 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js
@@ -23,18 +23,20 @@ const Bytes = require('dw/util/Bytes');
const MessageDigest = require('dw/crypto/MessageDigest');
const Encoding = require('dw/crypto/Encoding');
const CustomerMgr = require('dw/customer/CustomerMgr');
-const constants = require('*/cartridge/adyen/config/constants');
-const AdyenConfigs = require('*/cartridge/adyen/utils/adyenConfigs');
const Transaction = require('dw/system/Transaction');
const UUIDUtils = require('dw/util/UUIDUtils');
-const collections = require('*/cartridge/scripts/util/collections');
const ShippingMgr = require('dw/order/ShippingMgr');
-const ShippingMethodModel = require('*/cartridge/models/shipping/shippingMethod');
const PaymentInstrument = require('dw/order/PaymentInstrument');
const StringUtils = require('dw/util/StringUtils');
+const Money = require('dw/value/Money');
+const TaxMgr = require('dw/order/TaxMgr');
+const ShippingLocation = require('dw/order/ShippingLocation');
//script includes
+const ShippingMethodModel = require('*/cartridge/models/shipping/shippingMethod');
+const collections = require('*/cartridge/scripts/util/collections');
+const constants = require('*/cartridge/adyen/config/constants');
+const AdyenConfigs = require('*/cartridge/adyen/utils/adyenConfigs');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
-const BasketMgr = require('dw/order/BasketMgr');
/* eslint no-var: off */
let adyenHelperObj = {
@@ -77,15 +79,50 @@ let adyenHelperObj = {
return null;
},
+ /**
+ * Returns shippingCost including taxes for a specific Shipment / ShippingMethod pair including the product level shipping cost if any
+ * @param {dw.order.ShippingMethod} shippingMethod - the default shipment of the current basket
+ * @param {dw.order.Shipment} shipment - a shipment of the current basket
+ * @returns {{currencyCode: String, value: String}} - Shipping Cost including taxes
+ */
getShippingCost(shippingMethod, shipment) {
+ const { shippingAddress } = shipment
const shipmentShippingModel = ShippingMgr.getShipmentShippingModel(shipment);
- const shippingCost = shipmentShippingModel.getShippingCost(shippingMethod);
+ let shippingCost = shipmentShippingModel.getShippingCost(shippingMethod).getAmount();
+ collections.forEach(shipment.getProductLineItems(), (lineItem) => {
+ const product = lineItem.getProduct();
+ const productQuantity = lineItem.getQuantity();
+ const productShippingModel = ShippingMgr.getProductShippingModel(product);
+ let productShippingCost = productShippingModel.getShippingCost(shippingMethod)
+ ? productShippingModel.getShippingCost(shippingMethod).getAmount().multiply(productQuantity)
+ : new Money(0, product.getPriceModel().getPrice().getCurrencyCode());
+ shippingCost = shippingCost.add(productShippingCost);
+ })
+ shippingCost = shippingAddress ? shippingCost.addRate(adyenHelperObj.getShippingTaxRate(shippingMethod, shippingAddress)) : shippingCost;
return {
- value: shippingCost.amount.value,
- currencyCode: shippingCost.amount.currencyCode,
+ value: shippingCost.getValue(),
+ currencyCode: shippingCost.getCurrencyCode(),
};
},
+ /**
+ * Returns tax rate for specific Shipment / ShippingMethod pair.
+ * @param {dw.order.ShippingMethod} shippingMethod - the default shipment of the current basket
+ * @param {dw.order.shippingAddress} shippingAddress - shippingAddress for the default shipment
+ * @returns {Number} - tax rate in decimals.(eg.: 0.02 for 2%)
+ */
+ getShippingTaxRate(shippingMethod, shippingAddress) {
+ const taxClassID = shippingMethod.getTaxClassID();
+ const taxJurisdictionID = TaxMgr.getTaxJurisdictionID(new ShippingLocation(shippingAddress));
+ return TaxMgr.getTaxRate(taxClassID, taxJurisdictionID);
+ },
+
+ /**
+ * Returns applicable shipping methods for specific Shipment / ShippingAddress pair.
+ * @param {dw.order.OrderAddress} address - the shipping address of the default shipment of the current basket
+ * @param {dw.order.Shipment} shipment - a shipment of the current basket
+ * @returns {dw.util.ArrayList
| null} - list of applicable shipping methods or null
+ */
getShippingMethods(shipment, address) {
if (!shipment) return null;
@@ -103,13 +140,35 @@ let adyenHelperObj = {
return shippingMethods;
},
+ /**
+ * Returns shipment UUID for the shipment.
+ * @param {dw.order.Shipment} shipment - a shipment of the current basket
+ * @returns {String | null} - shipment UUID or null
+ */
getShipmentUUID(shipment) {
if (!shipment) return null;
return shipment.UUID;
},
+ /**
+ * @typedef {object} ApplicableShippingMethodModel
+ * @property {string|null} ID
+ * @property {string|null} displayName
+ * @property {string|null} estimatedArrivalTime
+ * @property {boolean|null} default
+ * @property {boolean|null} [selected]
+ * @property {{currencyCode: String, value: String}} shippingCost
+ * @property {string|null} shipmentUUID
+ */
+
+ /**
+ * Returns applicable shipping methods(excluding store pickup methods) for specific Shipment / ShippingAddress pair.
+ * @param {dw.order.OrderAddress} address - the shipping address of the default shipment of the current basket
+ * @param {dw.order.Shipment} shipment - a shipment of the current basket
+ * @returns {dw.util.ArrayList | null} - list of applicable shipping methods or null
+ */
getApplicableShippingMethods(shipment, address) {
- const shippingMethods = this.getShippingMethods(shipment, address);
+ const shippingMethods = adyenHelperObj.getShippingMethods(shipment, address);
if (!shippingMethods) {
return null;
}
@@ -122,8 +181,8 @@ let adyenHelperObj = {
shippingMethod,
shipment,
);
- const shippingCost = this.getShippingCost(shippingMethod, shipment);
- const shipmentUUID = this.getShipmentUUID(shipment);
+ const shippingCost = adyenHelperObj.getShippingCost(shippingMethod, shipment);
+ const shipmentUUID = adyenHelperObj.getShipmentUUID(shipment);
filteredMethods.push({
...shippingMethodModel,
shippingCost,
@@ -135,26 +194,6 @@ let adyenHelperObj = {
return filteredMethods;
},
- callGetShippingMethods(shippingAddress) {
- let address;
- try {
- address = {
- city: shippingAddress.city,
- countryCode: shippingAddress.countryCode,
- stateCode: shippingAddress.stateOrRegion,
- };
- const currentBasket = BasketMgr.getCurrentBasket();
- const currentShippingMethodsModels = this.getApplicableShippingMethods(
- currentBasket.getDefaultShipment(),
- address,
- );
- return currentShippingMethodsModels;
- } catch (error) {
- AdyenLogs.error_log('Failed to fetch shipping methods');
- AdyenLogs.error_log(error);
- }
- },
-
getAdyenGivingConfig(order) {
if (!order.getPaymentInstruments(
adyenHelperObj.getOrderMainPaymentInstrumentType(order),
@@ -293,7 +332,7 @@ let adyenHelperObj = {
return returnValue;
},
- // determines whether Adyen Giving is available based on the donation token
+ // determines whether Adyen Giving is available based on the donation token
isAdyenGivingAvailable(paymentInstrument) {
return paymentInstrument.paymentTransaction.custom.Adyen_donationToken;
},
@@ -337,6 +376,13 @@ let adyenHelperObj = {
return false;
},
+ isPayPalExpress(paymentMethod){
+ if (paymentMethod.type === 'paypal' && paymentMethod.subtype === 'express'){
+ return true;
+ }
+ return false;
+ },
+
// Get stored card token of customer saved card based on matched cardUUID
getCardToken(cardUUID, customer) {
let token = '';
@@ -489,29 +535,12 @@ let adyenHelperObj = {
},
// creates a request object to send to the Adyen Checkout API
- createAdyenRequestObject(order, paymentInstrument) {
+ createAdyenRequestObject(orderNo, orderToken, paymentInstrument) {
const jsonObject = JSON.parse(paymentInstrument.custom.adyenPaymentData);
const filteredJson = adyenHelperObj.validateStateData(jsonObject);
const { stateData } = filteredJson;
- let reference = 'recurringPayment-account';
- let orderToken = 'recurringPayment-token';
- if (order && order.getOrderNo()) {
- reference = order.getOrderNo();
- orderToken = order.getOrderToken();
- }
-
- let signature = '';
- //Create signature to verify returnUrl if there is an order
- if (order && order.getUUID()) {
- signature = adyenHelperObj.createSignature(
- paymentInstrument,
- order.getUUID(),
- reference,
- );
- }
-
// Add recurringProcessingModel in case shopper wants to save the card from checkout
if (stateData.storePaymentMethod){
stateData.recurringProcessingModel = constants.RECURRING_PROCESSING_MODEL.CARD_ON_FILE;
@@ -525,22 +554,21 @@ let adyenHelperObj = {
}
stateData.merchantAccount = AdyenConfigs.getAdyenMerchantAccount();
- stateData.reference = reference;
- stateData.returnUrl = URLUtils.https(
- 'Adyen-ShowConfirmation',
- 'merchantReference',
- reference,
- 'signature',
- signature,
- 'orderToken',
- orderToken,
- ).toString();
+ stateData.reference = orderNo;
+ stateData.returnUrl = adyenHelperObj.createRedirectUrl(paymentInstrument, orderNo, orderToken)
stateData.applicationInfo = adyenHelperObj.getApplicationInfo();
stateData.additionalData = {};
return stateData;
},
+ /**
+ * Returns unique hashed signature.
+ * @param {dw.order.OrderPaymentInstrument} paymentInstrument - paymentInstrument for the current order or current basket.
+ * @param {String} value - UUID to be hashed for creating signature.
+ * @param {String} salt - order number for the current order or from createOrderNo() used as Salt for hash.
+ * @returns {String} - returns hashed signature.
+ */
createSignature(paymentInstrument, value, salt) {
const newSignature = adyenHelperObj.getAdyenHash(value, salt);
Transaction.wrap(function () {
@@ -549,6 +577,33 @@ let adyenHelperObj = {
return newSignature;
},
+ /**
+ * Returns redirectURL with 'Adyen-ShowConfirmation' route and query params .
+ * @param {dw.order.OrderPaymentInstrument} paymentInstrument - paymentInstrument for the current order or current basket
+ * @param {String} orderNo - order number for the current order or from createOrderNo()
+ * @param {String} [orderToken] - orderToken for current order if order exists
+ * @returns {String} - returns String representation of the redirectURL
+ */
+ createRedirectUrl(paymentInstrument, orderNo, orderToken) {
+ if(!(paymentInstrument instanceof dw.order.OrderPaymentInstrument)) {
+ return null
+ }
+ const signature = adyenHelperObj.createSignature(
+ paymentInstrument,
+ UUIDUtils.createUUID(),
+ orderNo,
+ );
+ return URLUtils.https(
+ 'Adyen-ShowConfirmation',
+ 'merchantReference',
+ orderNo,
+ 'signature',
+ signature,
+ 'orderToken',
+ orderToken,
+ ).toString();
+ },
+
// adds 3DS2 fields to an Adyen Checkout payments Request
add3DS2Data(jsonObject) {
jsonObject.authenticationData = {
@@ -573,6 +628,9 @@ let adyenHelperObj = {
case 'amazonpay':
methodName = 'Amazon Pay';
break;
+ case 'paypal':
+ methodName = 'PayPal';
+ break;
default:
methodName = paymentMethod;
}
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/paypalHelper.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/paypalHelper.js
new file mode 100644
index 000000000..bd912dab8
--- /dev/null
+++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/paypalHelper.js
@@ -0,0 +1,177 @@
+/**
+ * ######
+ * ######
+ * ############ ####( ###### #####. ###### ############ ############
+ * ############# #####( ###### #####. ###### ############# #############
+ * ###### #####( ###### #####. ###### ##### ###### ##### ######
+ * ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
+ * ###### ###### #####( ###### #####. ###### ##### ##### ######
+ * ############# ############# ############# ############# ##### ######
+ * ############ ############ ############# ############ ##### ######
+ * ######
+ * #############
+ * ############
+ * Adyen Salesforce Commerce Cloud
+ * Copyright (c) 2021 Adyen B.V.
+ * This file is open source and available under the MIT license.
+ * See the LICENSE file for more info.
+ *
+ * Add all product and shipping line items to request
+ */
+const Money = require('dw/value/Money');
+const Transaction = require('dw/system/Transaction');
+const LineItemHelper = require('*/cartridge/adyen/utils/lineItemHelper');
+const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
+
+const PAYPAL_ITEM_CATEGORY = ['PHYSICAL_GOODS', 'DIGITAL_GOODS', 'DONATION'];
+function getLineItems({ Order: order, Basket: basket }) {
+ if (!(order || basket)) return null;
+ const orderOrBasket = order || basket;
+ const allLineItems = LineItemHelper.getAllLineItems(
+ orderOrBasket.getAllLineItems(),
+ );
+ return allLineItems.map((lineItem) => {
+ const lineItemObject = {};
+ const description = LineItemHelper.getDescription(lineItem);
+ const id = LineItemHelper.getId(lineItem);
+ const quantity = LineItemHelper.getQuantity(lineItem);
+ const itemAmount = LineItemHelper.getItemAmount(lineItem).divide(quantity);
+ const vatAmount = LineItemHelper.getVatAmount(lineItem).divide(quantity);
+ // eslint-disable-next-line
+ if (lineItem.hasOwnProperty('category')) {
+ if (PAYPAL_ITEM_CATEGORY.indexOf(lineItem.category) > -1) {
+ lineItemObject.itemCategory = lineItem.category;
+ }
+ }
+ lineItemObject.quantity = quantity;
+ lineItemObject.description = description;
+ lineItemObject.sku = id;
+ lineItemObject.amountExcludingTax = itemAmount.getValue().toFixed();
+ lineItemObject.taxAmount = vatAmount.getValue().toFixed();
+ return lineItemObject;
+ });
+}
+
+/**
+ * @typedef {object} paypalShippingOption
+ * @property {string} reference - shipping method id
+ * @property {string} description - shipping method displayName
+ * @property {('Shipping')} type
+ * @property {{currencyCode: String, value: String}} amount
+ * - shipping cost for shipping method including tax
+ * @property {boolean} selected - - shipping method is selected
+ */
+
+/**
+ * @typedef {object} paypalUpdateOrderRequest
+ * @property {String} pspReference - the pspReference returned from adyen /payments endpoint
+ * @property {String} paymentData - encrypted payment data from paypal component
+ * @property {{currencyCode: String, value: String}} amount
+ * - adjustedMerchandizeTotalGrossPrice + adjustedShippingTotalGrossPrice
+ * @property {dw.util.ArrayList} deliveryMethods
+ * - list of paypalShippingOption
+ */
+
+/**
+ * Returns applicable shipping methods(excluding store pickup methods)
+ * for specific Shipment / ShippingAddress pair.
+ * @param {String} pspReference - the pspReference returned from adyen /payments endpoint
+ * @param {dw.order.basket} amount - a shipment of the current basket
+ * @param {dw.util.ArrayList} currentShippingMethods
+ * - a shipment of the current basket
+ * @param {String} paymentData - encrypted payment data from paypal component
+ * @returns {paypalUpdateOrderRequest} - list of applicable shipping methods or null
+ */
+function createPaypalUpdateOrderRequest(
+ pspReference,
+ currentBasket,
+ currentShippingMethods,
+ paymentData,
+) {
+ const adjustedShippingTotalGrossPrice = {
+ currency: currentBasket.currencyCode,
+ value: AdyenHelper.getCurrencyValueForApi(
+ currentBasket.getAdjustedShippingTotalGrossPrice(),
+ ).value,
+ };
+ const adjustedMerchandizeTotalGrossPrice = {
+ currency: currentBasket.currencyCode,
+ value:
+ AdyenHelper.getCurrencyValueForApi(
+ currentBasket.getAdjustedMerchandizeTotalGrossPrice(),
+ ).value + adjustedShippingTotalGrossPrice.value,
+ };
+ const deliveryMethods = currentShippingMethods.map((shippingMethod) => {
+ const { currencyCode, value } = shippingMethod.shippingCost;
+ return {
+ reference: shippingMethod.ID,
+ description: shippingMethod.displayName,
+ type: 'Shipping',
+ amount: {
+ currency: currencyCode,
+ value: AdyenHelper.getCurrencyValueForApi(
+ new Money(value, currencyCode),
+ ).value,
+ },
+ selected: shippingMethod.selected,
+ };
+ });
+ return {
+ pspReference,
+ paymentData,
+ amount: adjustedMerchandizeTotalGrossPrice,
+ deliveryMethods,
+ };
+}
+
+/**
+ * sets Shipping and Billing address for the basket
+ * @param {dw.order.Basket} currentBasket - the current basket
+ * @returns {undefined}
+ */
+function setBillingAndShippingAddress(currentBasket) {
+ let { billingAddress } = currentBasket;
+ let { shippingAddress } = currentBasket.getDefaultShipment();
+ Transaction.wrap(() => {
+ if (!shippingAddress) {
+ shippingAddress = currentBasket
+ .getDefaultShipment()
+ .createShippingAddress();
+ }
+ if (!billingAddress) {
+ billingAddress = currentBasket.createBillingAddress();
+ }
+ });
+
+ const shopperDetails = JSON.parse(session.privacy.shopperDetails);
+
+ Transaction.wrap(() => {
+ billingAddress.setFirstName(shopperDetails.shopperName.firstName);
+ billingAddress.setLastName(shopperDetails.shopperName.lastName);
+ billingAddress.setAddress1(shopperDetails.billingAddress.street);
+ billingAddress.setCity(shopperDetails.billingAddress.city);
+ billingAddress.setPhone(shopperDetails.telephoneNumber);
+ billingAddress.setPostalCode(shopperDetails.billingAddress.postalCode);
+ billingAddress.setStateCode(shopperDetails.billingAddress.stateOrProvince);
+ billingAddress.setCountryCode(shopperDetails.billingAddress.country);
+
+ shippingAddress.setFirstName(shopperDetails.shopperName.firstName);
+ shippingAddress.setLastName(shopperDetails.shopperName.lastName);
+ shippingAddress.setAddress1(shopperDetails.shippingAddress.street);
+ shippingAddress.setCity(shopperDetails.shippingAddress.city);
+ shippingAddress.setPhone(shopperDetails.telephoneNumber);
+ shippingAddress.setPostalCode(shopperDetails.shippingAddress.postalCode);
+ shippingAddress.setStateCode(
+ shopperDetails.shippingAddress.stateOrProvince,
+ );
+ shippingAddress.setCountryCode(shopperDetails.shippingAddress.country);
+
+ currentBasket.setCustomerEmail(shopperDetails.shopperEmail);
+ });
+}
+
+module.exports = {
+ createPaypalUpdateOrderRequest,
+ getLineItems,
+ setBillingAndShippingAddress,
+};
diff --git a/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js b/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js
index c8295dcbc..c487831a1 100644
--- a/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js
+++ b/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js
@@ -20,12 +20,19 @@ server.post(
adyen.paymentsDetails,
);
-server.get(
+/**
+ * Save shipping address to currentBasket and
+ * get applicable shipping methods from an Express component in the SFCC session
+ */
+server.post(
'ShippingMethods',
server.middleware.https,
adyen.callGetShippingMethods,
);
+/**
+ * Save selected shipping method to currentBasket from an Express component in the SFCC session
+ */
server.post(
'SelectShippingMethod',
server.middleware.https,
@@ -92,6 +99,15 @@ server.get(
adyen.getCheckoutPaymentMethods,
);
+/**
+ * Show the review page template.
+ */
+server.post(
+ 'CheckoutReview',
+ server.middleware.https,
+ adyen.handleCheckoutReview,
+);
+
/**
* Called by Adyen to update status of payments. It should always display [accepted] when finished.
*/
@@ -125,6 +141,28 @@ server.post(
*/
server.post('partialPayment', server.middleware.https, adyen.partialPayment);
+/**
+ * Called by Adyen to make /payments call for PayPal Express flow
+ */
+server.post(
+ 'MakeExpressPaymentsCall',
+ server.middleware.https,
+ adyen.makeExpressPaymentsCall,
+);
+
+/**
+ * Called by Adyen to make /paymentsDetails for PayPal Express flow
+ */
+server.post(
+ 'MakeExpressPaymentDetailsCall',
+ server.middleware.https,
+ adyen.makeExpressPaymentDetailsCall,
+);
+
+/**
+ * Called by Adyen to save the shopper data coming from PayPal Express
+ */
+server.post('SaveShopperData', server.middleware.https, adyen.saveShopperData);
/**
* Called by Adyen to fetch applied giftcards
*/
diff --git a/tests/playwright/fixtures/USD.spec.mjs b/tests/playwright/fixtures/USD.spec.mjs
index dba1ec5e6..41e3a262b 100644
--- a/tests/playwright/fixtures/USD.spec.mjs
+++ b/tests/playwright/fixtures/USD.spec.mjs
@@ -80,7 +80,7 @@ for (const environment of environments) {
test('PayPal Success @quick', async ({ page }) => {
redirectShopper = new RedirectShopper(page);
- await redirectShopper.doPayPalPayment();
+ await redirectShopper.doPayPalPayment(false, false, true);
await checkoutPage.expectSuccess();
});
});
@@ -251,4 +251,48 @@ for (const environment of environments) {
await accountPage.expectFailure();
});
});
+
+ test.describe.parallel(`${environment.name} USD`, () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`${environment.urlExtension}`);
+ });
+
+ test('PayPal Express @quick', async ({ page }) => {
+ checkoutPage = new environment.CheckoutPage(page);
+ await checkoutPage.addProductToCart();
+ await checkoutPage.navigateToCart(regionsEnum.US);
+ redirectShopper = new RedirectShopper(page);
+ await redirectShopper.doPayPalPayment(true, false, true);
+ if (environment.name.indexOf('v5') !== -1) {
+ await page.locator("button[value='place-order']").click();
+ await page.locator(".order-thank-you-msg").isVisible({ timeout: 20000 });
+ }
+ else {
+ await checkoutPage.expectSuccess();
+ }
+ });
+
+ test('PayPal Express shipping change @quick', async ({ page }) => {
+ checkoutPage = new environment.CheckoutPage(page);
+ await checkoutPage.addProductToCart();
+ await checkoutPage.navigateToCart(regionsEnum.US);
+ redirectShopper = new RedirectShopper(page);
+ await redirectShopper.doPayPalPayment(true, true, true);
+ if (environment.name.indexOf('v5') !== -1) {
+ await page.locator("button[value='place-order']").click();
+ await page.locator(".order-thank-you-msg").isVisible({ timeout: 20000 });
+ }
+ else {
+ await checkoutPage.expectSuccess();
+ }
+ });
+
+ test('PayPal Express Cancellation @quick', async ({ page }) => {
+ checkoutPage = new environment.CheckoutPage(page);
+ await checkoutPage.addProductToCart();
+ await checkoutPage.navigateToCart(regionsEnum.US);
+ redirectShopper = new RedirectShopper(page);
+ await redirectShopper.doPayPalPayment(true, false, false);
+ });
+ });
}
diff --git a/tests/playwright/pages/PaymentMethodsPage.mjs b/tests/playwright/pages/PaymentMethodsPage.mjs
index c7ce85447..cdf64f94b 100644
--- a/tests/playwright/pages/PaymentMethodsPage.mjs
+++ b/tests/playwright/pages/PaymentMethodsPage.mjs
@@ -32,16 +32,18 @@ export default class PaymentMethodsPage {
await iDealInput.click();
};
- initiatePayPalPayment = async () => {
+ initiatePayPalPayment = async (expressFlow, shippingChange, success) => {
// Paypal button locator on payment methods page
const payPalButton = this.page
.frameLocator('.adyen-checkout__paypal__button--paypal iframe.visible')
.locator('.paypal-button');
// Click PayPal radio button
- await this.page.click('#rb_paypal');
- await expect(this.page.locator('.adyen-checkout__paypal__button--paypal iframe.visible'),).toBeVisible({ timeout: 20000 });
-
+ if (!expressFlow) {
+ await this.page.click('#rb_paypal');
+ }
+ await expect(this.page.locator('.adyen-checkout__paypal__button--paypal iframe.visible'),).toBeVisible({ timeout: 20000 });
+
// Capture popup for interaction
const [popup] = await Promise.all([
this.page.waitForEvent('popup'),
@@ -60,13 +62,29 @@ export default class PaymentMethodsPage {
this.passwordInput = popup.locator('#password');
this.loginButton = popup.locator('#btnLogin');
this.agreeAndPayNowButton = popup.locator('#payment-submit-btn');
+ this.shippingMethodsDropdown = popup.locator('#shippingMethodsDropdown');
+ this.cancelButton = popup.locator('a[data-testid="cancel-link"]');
await this.emailInput.click();
await this.emailInput.fill(paymentData.PayPal.username);
await this.nextButton.click();
await this.passwordInput.fill(paymentData.PayPal.password);
await this.loginButton.click();
- await this.agreeAndPayNowButton.click();
+ await this.page.waitForTimeout(5000);
+
+ if (shippingChange){
+ await this.shippingMethodsDropdown.selectOption({ index: 2 }); // This selects the second option as first one is hidden by default in paypal modale
+ await this.page.waitForTimeout(5000);
+ }
+
+ if (success) {
+ await this.agreeAndPayNowButton.click();
+ }
+ else {
+ await this.cancelButton.click();
+ await this.page.goBack();
+ await expect(this.page.locator('.add-to-cart'),).toBeVisible({ timeout: 20000 });
+ }
};
initiateAmazonPayment = async (
diff --git a/tests/playwright/paymentFlows/redirectShopper.mjs b/tests/playwright/paymentFlows/redirectShopper.mjs
index 3561584fd..7b8c84caf 100644
--- a/tests/playwright/paymentFlows/redirectShopper.mjs
+++ b/tests/playwright/paymentFlows/redirectShopper.mjs
@@ -29,8 +29,8 @@ export class RedirectShopper {
await this.paymentMethodsPage.initiateOneyPayment(shopper);
};
- doPayPalPayment = async () => {
- await this.paymentMethodsPage.initiatePayPalPayment();
+ doPayPalPayment = async (expressFlow, shippingChange, success) => {
+ await this.paymentMethodsPage.initiatePayPalPayment(expressFlow, shippingChange, success);
};
doAmazonPayment = async (normalFlow, selectedCard, success) => {