Repository: Cleeng/mediastore-sdk
Pull request #499: fix/MSSDK-2319: P3 - Newsmax  - Cleeng payment issues
Author: login: m4tewoosh
Labels: name: fix
description: Used for regular fixes of the code. Updates minor version of the package on release.
Created: 2025-05-08T14:47:57Z | Merged: 2025-05-14T09:16:40Z

### Description

To add detailed error message when payment details update fails because of no active entitlement




Diff:
diff --git a/src/appRedux/finalizeAddPaymentDetailsSlice.js b/src/appRedux/finalizeAddPaymentDetailsSlice.js
deleted file mode 100644
index 6e4697752..000000000
--- a/src/appRedux/finalizeAddPaymentDetailsSlice.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
-import { finalizeAddPaymentDetails } from 'api';
-import eventDispatcher, {
-  MSSDK_UPDATE_PAYMENT_DETAILS_FAILED,
-  MSSDK_UPDATE_PAYMENT_DETAILS_SUCCESSFUL
-} from '../util/eventDispatcher';
-
-const initialState = {
-  loading: false,
-  paymentDetails: null,
-  error: null
-};
-
-export const fetchFinalizeAddPaymentDetails = createAsyncThunk(
-  'finalizeAddPaymentDetails',
-  async ({ paymentMethodId, details }, { getState, rejectWithValue }) => {
-    const {
-      paymentMethods: { selectedPaymentMethod }
-    } = getState();
-    try {
-      const { paymentDetails } = await finalizeAddPaymentDetails(
-        paymentMethodId || selectedPaymentMethod?.id,
-        details
-      );
-      return paymentDetails;
-    } catch (err) {
-      return rejectWithValue(err.message);
-    }
-  }
-);
-
-export const finalizeAddPaymentDetailsSlice = createSlice({
-  name: 'finalizeAddPaymentDetails',
-  initialState,
-  extraReducers: (builder) => {
-    builder.addCase(fetchFinalizeAddPaymentDetails.pending, (state) => {
-      state.loading = true;
-    });
-    builder.addCase(
-      fetchFinalizeAddPaymentDetails.fulfilled,
-      (state, { payload }) => {
-        state.loading = false;
-        state.paymentDetails = payload;
-        state.error = null;
-        eventDispatcher(MSSDK_UPDATE_PAYMENT_DETAILS_SUCCESSFUL, {
-          payload
-        });
-      }
-    );
-    builder.addCase(
-      fetchFinalizeAddPaymentDetails.rejected,
-      (state, { payload }) => {
-        state.loading = false;
-        state.error = payload;
-        eventDispatcher(MSSDK_UPDATE_PAYMENT_DETAILS_FAILED, {
-          payload
-        });
-      }
-    );
-  }
-});
-
-export default finalizeAddPaymentDetailsSlice.reducer;
diff --git a/src/appRedux/finalizeAddPaymentDetailsSlice.ts b/src/appRedux/finalizeAddPaymentDetailsSlice.ts
new file mode 100644
index 000000000..104dc020a
--- /dev/null
+++ b/src/appRedux/finalizeAddPaymentDetailsSlice.ts
@@ -0,0 +1,89 @@
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
+import { RootState } from './rootReducer';
+import { finalizeAddPaymentDetails as finalizeAddPaymentDetailsApi } from '../api';
+import eventDispatcher, {
+  MSSDK_UPDATE_PAYMENT_DETAILS_SUCCESSFUL
+} from '../util/eventDispatcher';
+import { PaymentDetails } from './types';
+
+type FinalizeAddPaymentDetailsState = {
+  loading: boolean;
+  paymentDetails: PaymentDetails | null;
+  error: string | null;
+};
+
+type FetchFinalizeAddPaymentDetailsArgs = {
+  paymentMethodId?: number | string;
+  details?: PaymentDetails;
+};
+
+const initialState: FinalizeAddPaymentDetailsState = {
+  loading: false,
+  paymentDetails: null,
+  error: null
+};
+
+export const fetchFinalizeAddPaymentDetails = createAsyncThunk<
+  PaymentDetails,
+  FetchFinalizeAddPaymentDetailsArgs,
+  { rejectValue: string; state: RootState }
+>(
+  'finalizeAddPaymentDetails/fetch',
+  async ({ paymentMethodId, details }, { getState, rejectWithValue }) => {
+    const {
+      paymentMethods: { selectedPaymentMethod }
+    } = getState();
+    try {
+      const response = await finalizeAddPaymentDetailsApi(
+        paymentMethodId ?? selectedPaymentMethod?.id,
+        details
+      );
+      return response.paymentDetails as PaymentDetails;
+    } catch (err) {
+      const error = err as Error;
+      return rejectWithValue(error.message);
+    }
+  }
+);
+
+export const finalizeAddPaymentDetailsSlice = createSlice({
+  name: 'finalizeAddPaymentDetails',
+  initialState,
+  reducers: {},
+  extraReducers: (builder) => {
+    builder.addCase(fetchFinalizeAddPaymentDetails.pending, (state) => {
+      state.loading = true;
+      state.error = null;
+    });
+    builder.addCase(
+      fetchFinalizeAddPaymentDetails.fulfilled,
+      (state, action: PayloadAction<PaymentDetails>) => {
+        state.loading = false;
+        state.paymentDetails = action.payload;
+        state.error = null;
+        eventDispatcher(MSSDK_UPDATE_PAYMENT_DETAILS_SUCCESSFUL, {
+          payload: action.payload
+        });
+      }
+    );
+    builder.addCase(
+      fetchFinalizeAddPaymentDetails.rejected,
+      (state, action: PayloadAction<string | undefined>) => {
+        state.loading = false;
+        state.error =
+          action.payload ?? 'Failed to finalize add payment details';
+      }
+    );
+  }
+});
+
+export const selectFinalizeAddPaymentDetailsLoading = (state: RootState) =>
+  state.finalizeAddPaymentDetails.loading;
+
+export const selectFinalizeAddPaymentDetailsResult = (state: RootState) =>
+  state.finalizeAddPaymentDetails.paymentDetails;
+
+export const selectFinalizeAddPaymentDetailsError = (state: RootState) =>
+  state.finalizeAddPaymentDetails.error;
+
+export default finalizeAddPaymentDetailsSlice.reducer;
diff --git a/src/appRedux/popupSlice.ts b/src/appRedux/popupSlice.ts
index 8c405e511..3f0a2b1ce 100644
--- a/src/appRedux/popupSlice.ts
+++ b/src/appRedux/popupSlice.ts
@@ -24,7 +24,7 @@ export const PAYMENT_DETAILS_STEPS = {
   ERROR: 'ERROR'
 } as const;
 
-const initialState: PopupManagerInitialState = {
+export const initialState: PopupManagerInitialState = {
   isOpen: false,
   isLoading: false,
   currentType: null,
diff --git a/src/appRedux/types/popupSlice.types.ts b/src/appRedux/types/popupSlice.types.ts
index 9eb73a099..37b370cfb 100644
--- a/src/appRedux/types/popupSlice.types.ts
+++ b/src/appRedux/types/popupSlice.types.ts
@@ -7,11 +7,12 @@ type Steps = (typeof PAYMENT_DETAILS_STEPS)[Keys];
 type PopupTypesKeys = keyof typeof POPUP_TYPES;
 type PopupTypes = (typeof POPUP_TYPES)[PopupTypesKeys];
 
-type PaymentDetails = {
+export type PaymentDetails = {
   isOpen: boolean;
   isLoading: boolean;
   step: Steps;
   initPaymentMethod: PaymentDetail | null;
+  errorMessage?: string;
 };
 
 type IsOpen = {
diff --git a/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.jsx b/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.jsx
deleted file mode 100644
index 8bdfa50b0..000000000
--- a/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import WarningIcon from 'assets/images/errors/warning.svg';
-import {
-  ContentStyled,
-  TitleStyled,
-  TextStyled,
-  ButtonWrapperStyled
-} from 'components/InnerPopupWrapper/InnerPopupWrapperStyled';
-import Button from 'components/Button';
-import { Trans, useTranslation } from 'react-i18next';
-import {
-  PAYMENT_DETAILS_STEPS,
-  updatePaymentDetailsPopup
-} from 'appRedux/popupSlice';
-import { ImageWrapper } from '../UpdatePaymentDetailsPopupStyled';
-
-const ErrorStep = () => {
-  const { t } = useTranslation();
-  const dispatch = useDispatch();
-  const { error } = useSelector((state) => state.finalizeAddPaymentDetails);
-
-  return (
-    <>
-      <ContentStyled>
-        <ImageWrapper>
-          <WarningIcon />
-        </ImageWrapper>
-        <TitleStyled>
-          {t('oops-something-went-wrong', 'Oops! Something went wrong.')}
-        </TitleStyled>
-        <TextStyled>
-          {error?.includes('Refused') ? (
-            <Trans i18nKey='update-payment-details-popup.refused'>
-              We weren’t able to update your payment method. <br /> Please try
-              again.
-            </Trans>
-          ) : (
-            <Trans i18nKey='update-payment-details-popup.error'>
-              We weren’t able to update your payment details. <br /> Please try
-              again using different payment method.
-            </Trans>
-          )}
-        </TextStyled>
-      </ContentStyled>
-      <ButtonWrapperStyled $removeMargin>
-        <Button
-          variant='simple'
-          onClickFn={() =>
-            dispatch(
-              updatePaymentDetailsPopup({
-                step: PAYMENT_DETAILS_STEPS.PAYMENT_DETAILS_UPDATE,
-                isLoading: false
-              })
-            )
-          }
-        >
-          {t('update-payment-details-popup.try-again', 'Try again')}
-        </Button>
-      </ButtonWrapperStyled>
-    </>
-  );
-};
-
-export default ErrorStep;
diff --git a/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.test.tsx b/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.test.tsx
new file mode 100644
index 000000000..ab0617262
--- /dev/null
+++ b/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.test.tsx
@@ -0,0 +1,119 @@
+// probably renderWithProviders is broken:
+// eslint-disable-next-line import/order
+import ErrorStep from './ErrorStep';
+import { screen } from '@testing-library/react';
+import { userEvent } from '@testing-library/user-event';
+import 'i18NextInit';
+import renderWithProviders from 'util/testHelpers';
+import { RootState } from 'appRedux/rootReducer';
+import {
+  PAYMENT_DETAILS_STEPS,
+  initialState as popupManagerInitialState,
+  updatePaymentDetailsPopup
+} from 'appRedux/popupSlice';
+
+vi.mock('appRedux/popupSlice', async (importOriginal) => ({
+  ...(await importOriginal<typeof import('appRedux/popupSlice')>()),
+  updatePaymentDetailsPopup: vi.fn(() => () => null)
+}));
+
+type StoreArgs = {
+  popupManagerError?: string;
+  finalizeAddPaymentDetailsError?: string;
+};
+
+const store = ({
+  popupManagerError,
+  finalizeAddPaymentDetailsError
+}: StoreArgs): Partial<RootState> => ({
+  popupManager: {
+    ...popupManagerInitialState,
+    paymentDetails: {
+      isOpen: true,
+      step: PAYMENT_DETAILS_STEPS.ERROR,
+      isLoading: false,
+      initPaymentMethod: null,
+      errorMessage: popupManagerError
+    }
+  },
+  finalizeAddPaymentDetails: {
+    loading: false,
+    paymentDetails: null,
+    error: finalizeAddPaymentDetailsError ?? null
+  }
+});
+
+describe('ErrorStep component', () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  test('should render ErrorStep component with default error message', () => {
+    renderWithProviders(<ErrorStep />, {
+      preloadedState: store({})
+    });
+
+    expect(screen.getByLabelText('Warning icon')).toBeVisible();
+    expect(screen.getByText('Oops! Something went wrong.')).toBeVisible();
+    expect(
+      screen.getByText(
+        'We weren’t able to update your payment details. Please try again using different payment method.'
+      )
+    ).toBeVisible();
+  });
+
+  test('should fire updatePaymentDetailsPopup function on button click', async () => {
+    renderWithProviders(<ErrorStep />, {
+      preloadedState: store({})
+    });
+
+    const user = userEvent.setup();
+    const button = screen.getByRole('button', {
+      name: 'Try again'
+    });
+    await user.click(button);
+
+    expect(updatePaymentDetailsPopup).toHaveBeenCalledWith({
+      step: PAYMENT_DETAILS_STEPS.PAYMENT_DETAILS_UPDATE,
+      isLoading: false
+    });
+  });
+
+  test('should show popupManager error message when payment is refused', async () => {
+    renderWithProviders(<ErrorStep />, {
+      preloadedState: store({ popupManagerError: 'Refused' })
+    });
+
+    expect(
+      screen.getByText(
+        'We weren’t able to update your payment method. Please try again.'
+      )
+    ).toBeVisible();
+  });
+
+  test('should show popupManager error message when user has no active entitlement', async () => {
+    renderWithProviders(<ErrorStep />, {
+      preloadedState: store({ popupManagerError: 'No active entitlement' })
+    });
+
+    expect(
+      screen.getByText(
+        'We weren’t able to update your payment method because you don’t have an active plan.'
+      )
+    ).toBeVisible();
+  });
+
+  test('should show finalizeAddPaymentDetails default error message when payment is cancelled', async () => {
+    renderWithProviders(<ErrorStep />, {
+      preloadedState: store({
+        finalizeAddPaymentDetailsError: 'Cancelled'
+      })
+    });
+
+    expect(
+      screen.getByText(
+        'We weren’t able to update your payment details. Please try again using different payment method.'
+      )
+    ).toBeVisible();
+  });
+});
diff --git a/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.tsx b/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.tsx
new file mode 100644
index 000000000..9db880e70
--- /dev/null
+++ b/src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.tsx
@@ -0,0 +1,90 @@
+import { Trans, useTranslation } from 'react-i18next';
+import WarningIcon from 'assets/images/errors/warning.svg';
+import {
+  ContentStyled,
+  TitleStyled,
+  TextStyled,
+  ButtonWrapperStyled
+} from 'components/InnerPopupWrapper/InnerPopupWrapperStyled';
+import Button from 'components/Button';
+import { useAppDispatch, useAppSelector } from 'appRedux/store';
+import {
+  PAYMENT_DETAILS_STEPS,
+  selectPaymentDetailsPopup,
+  updatePaymentDetailsPopup
+} from 'appRedux/popupSlice';
+import { ImageWrapper } from '../UpdatePaymentDetailsPopupStyled';
+
+const errorMessages = {
+  REFUSED: 'Refused',
+  NO_ACTIVE_ENTITLEMENT: 'No active entitlement'
+};
+
+const ErrorStep = () => {
+  const { t } = useTranslation();
+  const dispatch = useAppDispatch();
+  const { error: finalizeErrorMessage } = useAppSelector(
+    (state) => state.finalizeAddPaymentDetails
+  );
+  const { errorMessage } = useAppSelector(selectPaymentDetailsPopup);
+
+  const errorMessageValue = errorMessage ?? finalizeErrorMessage;
+
+  const getErrorMessage = () => {
+    if (errorMessageValue?.includes(errorMessages.REFUSED)) {
+      return (
+        <Trans i18nKey='update-payment-details-popup.refused'>
+          We weren’t able to update your payment method. <br /> Please try
+          again.
+        </Trans>
+      );
+    }
+
+    if (errorMessageValue?.includes(errorMessages.NO_ACTIVE_ENTITLEMENT)) {
+      return (
+        <Trans i18nKey='update-payment-details-popup.no-active-plan'>
+          We weren’t able to update your payment method because you don’t have
+          an active plan.
+        </Trans>
+      );
+    }
+
+    return (
+      <Trans i18nKey='update-payment-details-popup.error'>
+        We weren’t able to update your payment details. <br /> Please try again
+        using different payment method.
+      </Trans>
+    );
+  };
+
+  return (
+    <>
+      <ContentStyled>
+        <ImageWrapper>
+          <WarningIcon aria-label='Warning icon' />
+        </ImageWrapper>
+        <TitleStyled>
+          {t('oops-something-went-wrong', 'Oops! Something went wrong.')}
+        </TitleStyled>
+        <TextStyled>{getErrorMessage()}</TextStyled>
+      </ContentStyled>
+      <ButtonWrapperStyled $removeMargin>
+        <Button
+          variant='simple'
+          onClickFn={() =>
+            dispatch(
+              updatePaymentDetailsPopup({
+                step: PAYMENT_DETAILS_STEPS.PAYMENT_DETAILS_UPDATE,
+                isLoading: false
+              })
+            )
+          }
+        >
+          {t('update-payment-details-popup.try-again', 'Try again')}
+        </Button>
+      </ButtonWrapperStyled>
+    </>
+  );
+};
+
+export default ErrorStep;
diff --git a/src/components/UpdatePaymentDetailsPopup/UpdatePaymentDetailsPopup.jsx b/src/components/UpdatePaymentDetailsPopup/UpdatePaymentDetailsPopup.jsx
index 1a39ee0bf..5743c8a1b 100644
--- a/src/components/UpdatePaymentDetailsPopup/UpdatePaymentDetailsPopup.jsx
+++ b/src/components/UpdatePaymentDetailsPopup/UpdatePaymentDetailsPopup.jsx
@@ -212,7 +212,10 @@ const UpdatePaymentDetailsPopup = () => {
     if (errors.length) {
       eventDispatcher(MSSDK_UPDATE_PAYMENT_DETAILS_FAILED);
       dispatch(
-        updatePaymentDetailsPopup({ step: PAYMENT_DETAILS_STEPS.ERROR })
+        updatePaymentDetailsPopup({
+          step: PAYMENT_DETAILS_STEPS.ERROR,
+          errorMessage: errors[0]
+        })
       );
       return;
     }
diff --git a/src/translations/en/translations.json b/src/translations/en/translations.json
index bb8046808..1858f8d68 100644
--- a/src/translations/en/translations.json
+++ b/src/translations/en/translations.json
@@ -57,20 +57,20 @@
   "checkout-price-box.button.redeem": "Redeem here",
   "checkout-price-box.coupon-discount": "Coupon Discount",
   "checkout-price-box.coupon-note-applied": "Promotional Pricing applied!",
-  "checkout-price-box.remove-coupon-discount": "[Remove]",
   "checkout-price-box.gift-code-label": "Have a gift code?",
   "checkout-price-box.payment-method-fee": "Payment Method Fee",
   "checkout-price-box.price": "Price",
+  "checkout-price-box.promo": "Promo",
+  "checkout-price-box.remove-coupon-discount": "[Remove]",
   "checkout-price-box.service-fee": "Service Fee",
   "checkout-price-box.total": "Today`s total",
   "checkout-price-box.trial-discount": "Trial Discount",
-  "checkout-price-box.promo": "Promo",
   "Confirm": "Confirm",
   "consents-general-error": "Unable to fetch terms & conditions. Publisher is not recognized",
   "Continue": "Continue",
+  "coupon-input.clear-coupon": "Clear coupon code",
   "coupon-input.error": "This is not a valid coupon code for this offer. Please check the code on your coupon and try again.",
   "coupon-input.placeholder": "Your coupon",
-  "coupon-input.clear-coupon": "Clear coupon code",
   "coupon-input.redeem": "Redeem",
   "coupon-input.redeem-coupon": "Redeem coupon",
   "coupon-input.success": "Your coupon has been applied!",
@@ -271,8 +271,8 @@
   "payment-methods.error.not-available": "Payment methods not available",
   "payment.complete-purchase": "Complete purchase",
   "payment.error.cannot-fetch-payment-methods": "Cannot fetch payment methods",
-  "payment.error.failed": "The payment failed. Please try again.",
   "payment.error.captcha-verification-failed": "An error occurred during payment. Please try again later. If the issue persists, please reach out to our support team for assistance.",
+  "payment.error.failed": "The payment failed. Please try again.",
   "payment.error.payment-method-not-supported": "Payment method not supported. Try different payment method",
   "payment.error.payment-methods-not-available": "Payment methods not available",
   "payment.error.payment-methods-not-defined": "Payment methods are not defined",
@@ -405,11 +405,11 @@
   "Register": "Register",
   "register-form.button.register": "Register",
   "register-form.error.account": "You would need our product <1>Core</1> to call this API",
+  "register-form.error.captcha-verification-failed": "An error occurred during registration. Please try again later. If the issue persists, please reach out to our support team for assistance.",
   "register-form.error.customer-exists": "Customer already exists.",
+  "register-form.error.email-verification-failed": "We couldn't verify the email address you entered. Please check it for accuracy and try again. If you're sure the address is correct and still see this message, you may need to use a different email or contact support for help.",
   "register-form.error.general": "An error occured.",
   "register-form.error.server-overloaded": "Server overloaded. Please try again later.",
-  "register-form.error.email-verification-failed": "We couldn't verify the email address you entered. Please check it for accuracy and try again. If you're sure the address is correct and still see this message, you may need to use a different email or contact support for help.",
-  "register-form.error.captcha-verification-failed": "An error occurred during registration. Please try again later. If the issue persists, please reach out to our support team for assistance.",
   "register-form.label.email": "E-mail",
   "register-form.label.password": "Password",
   "register.button.have-an-account": "Have an account?",
@@ -484,8 +484,8 @@
   "subscription-switches-list.error.recurring-process-already-started.subtitle": "Once the new one starts, you can switch to a new offer.",
   "subscription-switches-list.error.recurring-process-already-started.title": "You are at the end of the billing cycle for your current offer.",
   "subscription-switches-list.error.subscription-with-coupon-not-allowed.title": "You can't change your subscription if a coupon was applied. To change plan, please cancel your current subscription and purchase a new one",
-  "subscription-switches-list.offer-externally-managed-title": "Subscription is externally managed.",
   "subscription-switches-list.offer-externally-managed-subtitle": "Use an external service to change the plan.",
+  "subscription-switches-list.offer-externally-managed-title": "Subscription is externally managed.",
   "subscription-switches-list.offer-not-selected": "Click on the plan that you would like to switch from",
   "subscription-switches-list.switch-in-progress": "Subscription switch in progress!",
   "subscription-switches-list.switching-unavailable": "Looks like there aren't any options for switching from your current plan right now",
@@ -587,6 +587,7 @@
   "update-payment-details-popup.cannot-be-managed": "It looks like your payments cannot be managed from here",
   "update-payment-details-popup.current-method": "You are currently paying for your subscription via",
   "update-payment-details-popup.error": "We weren’t able to update your payment details. Please try again using different payment method.",
+  "update-payment-details-popup.no-active-plan": "We weren’t able to update your payment method because you don’t have an active plan.",
   "update-payment-details-popup.refused": "We weren’t able to update your payment method. Please try again.",
   "update-payment-details-popup.remove-method": "Remove your payment method",
   "update-payment-details-popup.success.back": "Back to Payment Details",


Reviews:
user: login: Saddage
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T08:59:30Z
user: login: Saddage
body: :medal_military: :medal_military: :medal_military: :medal_military: :medal_military: 

thanks for the tests!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-12T13:41:06Z

Review comments:
diff_hunk: @@ -0,0 +1,90 @@
+import { Trans, useTranslation } from 'react-i18next';
+import WarningIcon from 'assets/images/errors/warning.svg';
+import {
+  ContentStyled,
+  TitleStyled,
+  TextStyled,
+  ButtonWrapperStyled
+} from 'components/InnerPopupWrapper/InnerPopupWrapperStyled';
+import Button from 'components/Button';
+import { useAppDispatch, useAppSelector } from 'appRedux/store';
+import {
+  PAYMENT_DETAILS_STEPS,
+  selectPaymentDetailsPopup,
+  updatePaymentDetailsPopup
+} from 'appRedux/popupSlice';
+import { ImageWrapper } from '../UpdatePaymentDetailsPopupStyled';
+
+const errorMessages = {
+  REFUSED: 'Refused',
+  NO_ACTIVE_ENTITLEMENT: 'No active entitlement'
+};
+
+const ErrorStep = () => {
path: src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.tsx
user: login: Saddage
body: please add unit tests
created_at: 2025-05-09T08:59:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,119 @@
+// probably renderWithProviders is broken:
+// eslint-disable-next-line import/order
+import ErrorStep from './ErrorStep';
+import { screen } from '@testing-library/react';
+import { userEvent } from '@testing-library/user-event';
+import 'i18NextInit';
+import renderWithProviders from 'util/testHelpers';
+import { RootState } from 'appRedux/rootReducer';
+import {
+  PAYMENT_DETAILS_STEPS,
+  initialState as popupManagerInitialState,
+  updatePaymentDetailsPopup
+} from 'appRedux/popupSlice';
+
+vi.mock('appRedux/popupSlice', async (importOriginal) => ({
+  ...(await importOriginal<typeof import('appRedux/popupSlice')>()),
+  updatePaymentDetailsPopup: vi.fn(() => () => null)
+}));
+
+type StoreArgs = {
+  popupManagerError?: string;
+  finalizeAddPaymentDetailsError?: string;
+};
+
+const store = ({
+  popupManagerError,
+  finalizeAddPaymentDetailsError
+}: StoreArgs): Partial<RootState> => ({
+  popupManager: {
+    ...popupManagerInitialState,
+    paymentDetails: {
+      isOpen: true,
+      step: PAYMENT_DETAILS_STEPS.ERROR,
+      isLoading: false,
+      initPaymentMethod: null,
+      errorMessage: popupManagerError
+    }
+  },
+  finalizeAddPaymentDetails: {
+    loading: false,
+    paymentDetails: null,
+    error: finalizeAddPaymentDetailsError ?? null
+  }
+});
+
+describe('ErrorStep component', () => {
path: src/components/UpdatePaymentDetailsPopup/Steps/ErrorStep.test.tsx
user: login: Saddage
body: beautiful work :medal_military: 
created_at: 2025-05-12T13:40:48Z
author_association: COLLABORATOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-12T12:45:52Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Cleeng_mediastore-sdk&pullRequest=499) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=Cleeng_mediastore-sdk&pullRequest=499&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Cleeng_mediastore-sdk&pullRequest=499&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Cleeng_mediastore-sdk&pullRequest=499&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Cleeng_mediastore-sdk&pullRequest=499&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Cleeng_mediastore-sdk&pullRequest=499&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Cleeng_mediastore-sdk&pullRequest=499)


author_association: NONE