From f389d6285ea73189d1c67e4b8f60ccfc170dfb42 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Sat, 6 Apr 2024 00:23:47 +1100 Subject: [PATCH 01/10] chore: updated code to handle browser back button click --- src/payment/PaymentPage.jsx | 12 +++++++++++- src/payment/data/actions.js | 7 +++++++ src/payment/data/reducers.js | 6 +++++- src/payment/data/sagas.js | 2 ++ src/payment/data/selectors.js | 2 +- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/payment/PaymentPage.jsx b/src/payment/PaymentPage.jsx index 597ed74b9..fe567f164 100644 --- a/src/payment/PaymentPage.jsx +++ b/src/payment/PaymentPage.jsx @@ -3,12 +3,13 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { AppContext } from '@edx/frontend-platform/react'; +import { getConfig } from '@edx/frontend-platform'; import { sendPageEvent } from '@edx/frontend-platform/analytics'; import messages from './PaymentPage.messages'; // Actions -import { fetchBasket } from './data/actions'; +import { fetchBasket, fetchExistingBasket } from './data/actions'; // Selectors import { paymentSelector } from './data/selectors'; @@ -42,8 +43,17 @@ class PaymentPage extends React.Component { } componentDidMount() { + const sku = localStorage.getItem('sku'); + + // Check if SKU is not null + if (sku !== null) { + const paymentPage = `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=${sku}`; + window.location.href = paymentPage; + } + sendPageEvent(); this.props.fetchBasket(); + localStorage.removeItem('sku'); } renderContent() { diff --git a/src/payment/data/actions.js b/src/payment/data/actions.js index f3bfb7467..c3dc7f7fc 100644 --- a/src/payment/data/actions.js +++ b/src/payment/data/actions.js @@ -35,6 +35,13 @@ export const basketProcessing = isProcessing => ({ payload: isProcessing, }); +export const FETCH_EXISTING_BASKET = 'FETCH_EXISTING_BASKET'; + +export const fetchExistingBasket = sku => ({ + type: FETCH_EXISTING_BASKET, + payload: sku, +}); + export const CAPTURE_KEY_PROCESSING = 'CAPTURE_KEY_PROCESSING'; export const captureKeyProcessing = isProcessing => ({ diff --git a/src/payment/data/reducers.js b/src/payment/data/reducers.js index 204a08fbf..a3039f5d2 100644 --- a/src/payment/data/reducers.js +++ b/src/payment/data/reducers.js @@ -3,6 +3,7 @@ import { combineReducers } from 'redux'; import { BASKET_DATA_RECEIVED, BASKET_PROCESSING, + FETCH_EXISTING_BASKET, CAPTURE_KEY_DATA_RECEIVED, CAPTURE_KEY_PROCESSING, CLIENT_SECRET_DATA_RECEIVED, @@ -29,13 +30,16 @@ const basket = (state = basketInitialState, action = null) => { if (action !== null) { switch (action.type) { case fetchBasket.TRIGGER: return { ...state, loading: true }; + case FETCH_EXISTING_BASKET: return { ...state, loading: true }; case fetchBasket.FULFILL: return { ...state, loading: false, loaded: true, }; - case BASKET_DATA_RECEIVED: return { ...state, ...action.payload }; + case BASKET_DATA_RECEIVED: + localStorage.setItem('sku', action.payload.products[0].sku); + return { ...state, ...action.payload }; case BASKET_PROCESSING: return { ...state, diff --git a/src/payment/data/sagas.js b/src/payment/data/sagas.js index 8ab9c10e7..20dfb153d 100644 --- a/src/payment/data/sagas.js +++ b/src/payment/data/sagas.js @@ -16,6 +16,7 @@ import { captureKeyStartTimeout, microformStatus, fetchBasket, + fetchExistingBasket, addCoupon, removeCoupon, updateQuantity, @@ -260,6 +261,7 @@ export function* handleSubmitPayment({ payload }) { export default function* saga() { yield takeEvery(fetchCaptureKey.TRIGGER, handleFetchCaptureKey); + // yield takeEvery(fetchExistingBasket, handleFetchBasket); yield takeEvery(CAPTURE_KEY_START_TIMEOUT, handleCaptureKeyTimeout); yield takeEvery(fetchClientSecret.TRIGGER, handleFetchClientSecret); yield takeEvery(fetchBasket.TRIGGER, handleFetchBasket); diff --git a/src/payment/data/selectors.js b/src/payment/data/selectors.js index 5b1ae792b..1ad921b3b 100644 --- a/src/payment/data/selectors.js +++ b/src/payment/data/selectors.js @@ -41,7 +41,7 @@ export const paymentSelector = createSelector( ...basket, isCouponRedeemRedirect, isEmpty: - basket.loaded && !basket.redirect && (!basket.products || basket.products.length === 0), + basket.loaded && !basket.redirect && localStorage.getItem('sku') === null && (!basket.products || basket.products.length === 0), isRedirect: (basket.loaded && !!basket.redirect) || (!basket.loaded && isCouponRedeemRedirect), }; From 2cc54bc0c5bc9455e9870330eb50349566bbfc5d Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Mon, 8 Apr 2024 14:33:13 +1000 Subject: [PATCH 02/10] chore: updated code to handle browser back button click --- src/payment/PaymentPage.jsx | 7 ++++--- src/payment/data/reducers.js | 2 -- src/payment/data/sagas.js | 2 -- src/payment/data/selectors.js | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/payment/PaymentPage.jsx b/src/payment/PaymentPage.jsx index fe567f164..5ce79ccbf 100644 --- a/src/payment/PaymentPage.jsx +++ b/src/payment/PaymentPage.jsx @@ -9,7 +9,7 @@ import { sendPageEvent } from '@edx/frontend-platform/analytics'; import messages from './PaymentPage.messages'; // Actions -import { fetchBasket, fetchExistingBasket } from './data/actions'; +import { fetchBasket } from './data/actions'; // Selectors import { paymentSelector } from './data/selectors'; @@ -49,10 +49,11 @@ class PaymentPage extends React.Component { if (sku !== null) { const paymentPage = `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=${sku}`; window.location.href = paymentPage; + } else { + this.props.fetchBasket(); + sendPageEvent(); } - sendPageEvent(); - this.props.fetchBasket(); localStorage.removeItem('sku'); } diff --git a/src/payment/data/reducers.js b/src/payment/data/reducers.js index a3039f5d2..e83618d20 100644 --- a/src/payment/data/reducers.js +++ b/src/payment/data/reducers.js @@ -3,7 +3,6 @@ import { combineReducers } from 'redux'; import { BASKET_DATA_RECEIVED, BASKET_PROCESSING, - FETCH_EXISTING_BASKET, CAPTURE_KEY_DATA_RECEIVED, CAPTURE_KEY_PROCESSING, CLIENT_SECRET_DATA_RECEIVED, @@ -30,7 +29,6 @@ const basket = (state = basketInitialState, action = null) => { if (action !== null) { switch (action.type) { case fetchBasket.TRIGGER: return { ...state, loading: true }; - case FETCH_EXISTING_BASKET: return { ...state, loading: true }; case fetchBasket.FULFILL: return { ...state, loading: false, diff --git a/src/payment/data/sagas.js b/src/payment/data/sagas.js index 20dfb153d..8ab9c10e7 100644 --- a/src/payment/data/sagas.js +++ b/src/payment/data/sagas.js @@ -16,7 +16,6 @@ import { captureKeyStartTimeout, microformStatus, fetchBasket, - fetchExistingBasket, addCoupon, removeCoupon, updateQuantity, @@ -261,7 +260,6 @@ export function* handleSubmitPayment({ payload }) { export default function* saga() { yield takeEvery(fetchCaptureKey.TRIGGER, handleFetchCaptureKey); - // yield takeEvery(fetchExistingBasket, handleFetchBasket); yield takeEvery(CAPTURE_KEY_START_TIMEOUT, handleCaptureKeyTimeout); yield takeEvery(fetchClientSecret.TRIGGER, handleFetchClientSecret); yield takeEvery(fetchBasket.TRIGGER, handleFetchBasket); diff --git a/src/payment/data/selectors.js b/src/payment/data/selectors.js index 1ad921b3b..5b1ae792b 100644 --- a/src/payment/data/selectors.js +++ b/src/payment/data/selectors.js @@ -41,7 +41,7 @@ export const paymentSelector = createSelector( ...basket, isCouponRedeemRedirect, isEmpty: - basket.loaded && !basket.redirect && localStorage.getItem('sku') === null && (!basket.products || basket.products.length === 0), + basket.loaded && !basket.redirect && (!basket.products || basket.products.length === 0), isRedirect: (basket.loaded && !!basket.redirect) || (!basket.loaded && isCouponRedeemRedirect), }; From 6a34d3becd8eea6d4595a3fb0605f1216e3a863f Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Mon, 8 Apr 2024 14:45:31 +1000 Subject: [PATCH 03/10] fix: removed extra code --- src/payment/data/actions.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/payment/data/actions.js b/src/payment/data/actions.js index c3dc7f7fc..f3bfb7467 100644 --- a/src/payment/data/actions.js +++ b/src/payment/data/actions.js @@ -35,13 +35,6 @@ export const basketProcessing = isProcessing => ({ payload: isProcessing, }); -export const FETCH_EXISTING_BASKET = 'FETCH_EXISTING_BASKET'; - -export const fetchExistingBasket = sku => ({ - type: FETCH_EXISTING_BASKET, - payload: sku, -}); - export const CAPTURE_KEY_PROCESSING = 'CAPTURE_KEY_PROCESSING'; export const captureKeyProcessing = isProcessing => ({ From 91c877e116bf6ab3ffd1d12d35575ee780bbe3b2 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Mon, 8 Apr 2024 14:47:33 +1000 Subject: [PATCH 04/10] fix: updated code --- src/payment/PaymentPage.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/payment/PaymentPage.jsx b/src/payment/PaymentPage.jsx index 5ce79ccbf..0756237a4 100644 --- a/src/payment/PaymentPage.jsx +++ b/src/payment/PaymentPage.jsx @@ -49,12 +49,11 @@ class PaymentPage extends React.Component { if (sku !== null) { const paymentPage = `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=${sku}`; window.location.href = paymentPage; + localStorage.removeItem('sku'); } else { this.props.fetchBasket(); sendPageEvent(); } - - localStorage.removeItem('sku'); } renderContent() { From d5a059cd6cf294f07ed710315f90707092e4f40b Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Mon, 8 Apr 2024 18:53:00 +1000 Subject: [PATCH 05/10] fix: updated code to fix failing test --- src/payment/data/reducers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/payment/data/reducers.js b/src/payment/data/reducers.js index e83618d20..847233eec 100644 --- a/src/payment/data/reducers.js +++ b/src/payment/data/reducers.js @@ -36,7 +36,9 @@ const basket = (state = basketInitialState, action = null) => { }; case BASKET_DATA_RECEIVED: - localStorage.setItem('sku', action.payload.products[0].sku); + if (action.payload.products && action.payload.products.length > 0) { + localStorage.setItem('sku', action.payload.products[0].sku); + } return { ...state, ...action.payload }; case BASKET_PROCESSING: return { From 68b7c59cda24af8fb725dfbbb65221909f690867 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Tue, 16 Apr 2024 13:38:00 +1000 Subject: [PATCH 06/10] fix: incorporated feedback comments and update the code --- src/payment/PaymentPage.jsx | 20 ++++++++++++++++---- src/payment/checkout/Checkout.jsx | 8 ++++++++ src/payment/data/reducers.js | 3 --- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/payment/PaymentPage.jsx b/src/payment/PaymentPage.jsx index 0756237a4..ba26378d1 100644 --- a/src/payment/PaymentPage.jsx +++ b/src/payment/PaymentPage.jsx @@ -43,13 +43,25 @@ class PaymentPage extends React.Component { } componentDidMount() { - const sku = localStorage.getItem('sku'); + const rawSkus = localStorage.getItem('skus'); + const skus = JSON.parse(rawSkus); // Check if SKU is not null - if (sku !== null) { - const paymentPage = `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=${sku}`; + if (skus !== null) { + const baseURL = getConfig().ECOMMERCE_BASE_URL; + + // Constructing the URL with the sku parameters + let paymentPage = `${baseURL}/basket/add/?`; + // Appending each sku value to the URL + Object.values(skus).forEach(sku => { paymentPage += `sku=${sku}&`; }); + // for (const sku of skus) { + // paymentPage += `sku=${sku}&`; + // } + // Removing the extra '&' character at the end + paymentPage = paymentPage.slice(0, -1); + // const paymentPage = `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=${sku}`; window.location.href = paymentPage; - localStorage.removeItem('sku'); + localStorage.removeItem('skus'); } else { this.props.fetchBasket(); sendPageEvent(); diff --git a/src/payment/checkout/Checkout.jsx b/src/payment/checkout/Checkout.jsx index 7831f459a..d349bf7a4 100644 --- a/src/payment/checkout/Checkout.jsx +++ b/src/payment/checkout/Checkout.jsx @@ -47,6 +47,14 @@ class Checkout extends React.Component { ); this.props.submitPayment({ method: 'paypal' }); + + const { products } = this.props; + const skus = []; + + for (const product of products){ + skus.push(product.sku) + } + localStorage.setItem('skus', JSON.stringify(skus)); }; // eslint-disable-next-line react/no-unused-class-component-methods diff --git a/src/payment/data/reducers.js b/src/payment/data/reducers.js index 847233eec..fcf5dc41f 100644 --- a/src/payment/data/reducers.js +++ b/src/payment/data/reducers.js @@ -36,9 +36,6 @@ const basket = (state = basketInitialState, action = null) => { }; case BASKET_DATA_RECEIVED: - if (action.payload.products && action.payload.products.length > 0) { - localStorage.setItem('sku', action.payload.products[0].sku); - } return { ...state, ...action.payload }; case BASKET_PROCESSING: return { From d6e0b789399338ef2e56d43d82743750150727a6 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Tue, 16 Apr 2024 13:39:46 +1000 Subject: [PATCH 07/10] fix: removed unused code --- src/payment/PaymentPage.jsx | 5 ----- src/payment/checkout/Checkout.jsx | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/payment/PaymentPage.jsx b/src/payment/PaymentPage.jsx index ba26378d1..d868e6a72 100644 --- a/src/payment/PaymentPage.jsx +++ b/src/payment/PaymentPage.jsx @@ -49,17 +49,12 @@ class PaymentPage extends React.Component { // Check if SKU is not null if (skus !== null) { const baseURL = getConfig().ECOMMERCE_BASE_URL; - // Constructing the URL with the sku parameters let paymentPage = `${baseURL}/basket/add/?`; // Appending each sku value to the URL Object.values(skus).forEach(sku => { paymentPage += `sku=${sku}&`; }); - // for (const sku of skus) { - // paymentPage += `sku=${sku}&`; - // } // Removing the extra '&' character at the end paymentPage = paymentPage.slice(0, -1); - // const paymentPage = `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=${sku}`; window.location.href = paymentPage; localStorage.removeItem('skus'); } else { diff --git a/src/payment/checkout/Checkout.jsx b/src/payment/checkout/Checkout.jsx index d349bf7a4..c3c77eb90 100644 --- a/src/payment/checkout/Checkout.jsx +++ b/src/payment/checkout/Checkout.jsx @@ -52,7 +52,7 @@ class Checkout extends React.Component { const skus = []; for (const product of products){ - skus.push(product.sku) + skus.push(product.sku); } localStorage.setItem('skus', JSON.stringify(skus)); }; From 7b6c18ad56420354a17c82b6452fa6ac1f1a0971 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Tue, 16 Apr 2024 18:57:43 +1000 Subject: [PATCH 08/10] fix: disables eslint issues --- src/payment/checkout/Checkout.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/payment/checkout/Checkout.jsx b/src/payment/checkout/Checkout.jsx index c3c77eb90..23630fd38 100644 --- a/src/payment/checkout/Checkout.jsx +++ b/src/payment/checkout/Checkout.jsx @@ -48,10 +48,12 @@ class Checkout extends React.Component { this.props.submitPayment({ method: 'paypal' }); + // eslint-disable-next-line react/prop-types const { products } = this.props; const skus = []; - for (const product of products){ + // eslint-disable-next-line no-restricted-syntax + for (const product of products) { skus.push(product.sku); } localStorage.setItem('skus', JSON.stringify(skus)); From 3c796a4e4f23cbbb2b80cda3db84eaa966e844b5 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Wed, 17 Apr 2024 21:44:18 +1000 Subject: [PATCH 09/10] chore: added test case to check if skus are stored in localstorage --- src/payment/checkout/Checkout.test.jsx | 10 ++++++++++ src/setupTest.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/payment/checkout/Checkout.test.jsx b/src/payment/checkout/Checkout.test.jsx index f153eda09..27d67dc77 100644 --- a/src/payment/checkout/Checkout.test.jsx +++ b/src/payment/checkout/Checkout.test.jsx @@ -103,6 +103,16 @@ describe('', () => { expect(store.getActions().pop()).toEqual(submitPayment({ method: 'paypal' })); }); + it('should call submitPayment and store skus in localStorage when handleSubmitPayPal is called', async () => { + const paypalButton = await screen.findByTestId('PayPalButton'); + fireEvent.click(paypalButton); + + expect(store.getActions().pop()).toEqual(submitPayment({ method: 'paypal' })); + // Check if skus are stored in localStorage + const storedSkus = JSON.parse(localStorage.getItem('skus')); + expect(storedSkus.length).toBeGreaterThan(0); + }); + // Apple Pay temporarily disabled per REV-927 - https://github.com/openedx/frontend-app-payment/pull/256 it('submits and tracks the payment form', () => { diff --git a/src/setupTest.js b/src/setupTest.js index 3a65fb810..5c61ce7dc 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -18,3 +18,20 @@ mergeConfig({ APPLE_PAY_MERCHANT_CAPABILITIES: process.env.APPLE_PAY_MERCHANT_CAPABILITIES && process.env.APPLE_PAY_MERCHANT_CAPABILITIES.split(','), WAFFLE_FLAGS: {}, }); + +const localStorageMock = jest.fn(() => { + let store = {}; + return { + getItem: (key) => (store[key] || null), + setItem: (key, value) => { + store[key] = value.toString(); + }, + clear: () => { + store = {}; + }, + removeItem: (key) => { + delete store[key]; + }, + }; +})(); +Object.defineProperty(window, 'localStorage', { value: localStorageMock }); From 2a2e790418820ad09738a798b93e3c93b7bede44 Mon Sep 17 00:00:00 2001 From: zubair-ce07 Date: Wed, 17 Apr 2024 22:03:57 +1000 Subject: [PATCH 10/10] refactor: updated variable name --- src/payment/PaymentPage.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/payment/PaymentPage.jsx b/src/payment/PaymentPage.jsx index d868e6a72..5b7b4d132 100644 --- a/src/payment/PaymentPage.jsx +++ b/src/payment/PaymentPage.jsx @@ -50,12 +50,12 @@ class PaymentPage extends React.Component { if (skus !== null) { const baseURL = getConfig().ECOMMERCE_BASE_URL; // Constructing the URL with the sku parameters - let paymentPage = `${baseURL}/basket/add/?`; + let ecommerceBasketURL = `${baseURL}/basket/add/?`; // Appending each sku value to the URL - Object.values(skus).forEach(sku => { paymentPage += `sku=${sku}&`; }); + Object.values(skus).forEach(sku => { ecommerceBasketURL += `sku=${sku}&`; }); // Removing the extra '&' character at the end - paymentPage = paymentPage.slice(0, -1); - window.location.href = paymentPage; + ecommerceBasketURL = ecommerceBasketURL.slice(0, -1); + window.location.href = ecommerceBasketURL; localStorage.removeItem('skus'); } else { this.props.fetchBasket();