From 0594e5519e338f2d8117a55da0d8380fb2f2dcc7 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Thu, 7 May 2026 15:00:18 +0200 Subject: [PATCH 1/3] refactor(payments): cleaning up --- package.json | 2 +- src/app/analytics/ga.service.test.ts | 4 +- src/app/analytics/ga.service.ts | 4 +- src/app/analytics/googleSheet.service.ts | 2 +- src/app/analytics/impact.service.ts | 2 +- src/app/core/config/views.ts | 9 +- src/app/core/types.ts | 1 - src/app/i18n/locales/de.json | 3 +- src/app/i18n/locales/en.json | 3 +- src/app/i18n/locales/es.json | 3 +- src/app/i18n/locales/fr.json | 3 +- src/app/i18n/locales/it.json | 3 +- src/app/i18n/locales/ru.json | 3 +- src/app/i18n/locales/tw.json | 3 +- src/app/i18n/locales/zh.json | 3 +- src/app/routes/paths.json | 7 - src/app/store/index.ts | 2 - src/app/store/slices/products/index.ts | 83 ------------ src/services/auth.service.test.ts | 5 - src/services/auth.service.ts | 5 +- src/services/error.service.test.ts | 2 +- .../components/CheckoutProductCard.tsx | 41 +----- src/views/Checkout/hooks/useCheckout.test.ts | 6 - src/views/Checkout/hooks/useCheckout.ts | 9 +- .../Checkout/hooks/useInitializeCheckout.ts | 9 ++ src/views/Checkout/hooks/useProducts.test.ts | 27 ---- src/views/Checkout/hooks/useProducts.ts | 5 - .../Checkout/hooks/usePromotionalCode.ts | 2 +- .../Checkout/hooks/useUserPayment.test.ts | 11 -- src/views/Checkout/hooks/useUserPayment.ts | 10 +- .../services/checkout.service.test.ts | 7 - .../Checkout/services/checkout.service.ts | 29 ++--- src/views/Checkout/services/index.ts | 2 +- .../Checkout/services/payment.service.test.ts | 121 +----------------- .../Checkout/services/payment.service.ts | 99 -------------- .../Checkout/services/products.service.ts | 7 - .../Checkout/store/checkoutReducer.test.ts | 28 +--- src/views/Checkout/store/checkoutReducer.ts | 3 - src/views/Checkout/store/types.ts | 6 +- src/views/Checkout/types/checkout.types.ts | 1 - src/views/Checkout/types/index.ts | 19 +-- .../Checkout/utils/getProductAmount.test.ts | 2 +- src/views/Checkout/utils/getProductAmount.ts | 3 +- .../Checkout/utils/pcCloud.utils.test.ts | 81 ------------ src/views/Checkout/utils/pcCloud.utils.ts | 41 ------ src/views/Checkout/utils/utils.errors.ts | 6 - src/views/Checkout/views/CheckoutView.tsx | 28 +--- .../Checkout/views/CheckoutViewWrapper.tsx | 81 +++--------- src/views/Checkout/views/PcCloudSuccess.tsx | 67 ---------- src/views/Checkout/views/index.ts | 1 - .../utils/suscriptionUtils.test.ts | 1 - .../Signup/ShareGuestSignUpView.test.tsx | 5 - .../Signup/WorkspaceGuestSignUpView.test.tsx | 6 - .../Signup/utils/guestSignupOnSubmit.test.ts | 4 - src/views/Signup/utils/guestSignupOnSubmit.ts | 2 - yarn.lock | 112 +++------------- 56 files changed, 107 insertions(+), 927 deletions(-) delete mode 100644 src/app/store/slices/products/index.ts delete mode 100644 src/views/Checkout/utils/pcCloud.utils.test.ts delete mode 100644 src/views/Checkout/utils/pcCloud.utils.ts delete mode 100644 src/views/Checkout/utils/utils.errors.ts delete mode 100644 src/views/Checkout/views/PcCloudSuccess.tsx diff --git a/package.json b/package.json index 7b1fa9530..1f18957e9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@iconscout/react-unicons": "^1.1.6", "@internxt/css-config": "1.1.0", "@internxt/lib": "1.4.1", - "@internxt/sdk": "=1.15.13", + "@internxt/sdk": "=1.16.1", "@internxt/ui": "=0.1.15", "@phosphor-icons/react": "^2.1.7", "@popperjs/core": "^2.11.6", diff --git a/src/app/analytics/ga.service.test.ts b/src/app/analytics/ga.service.test.ts index 6968213d1..be2b09f00 100644 --- a/src/app/analytics/ga.service.test.ts +++ b/src/app/analytics/ga.service.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import gaService from './ga.service'; import { bytesToString } from 'app/drive/services/size.service'; -import { CouponCodeData } from 'views/Checkout/types'; import localStorageService from 'services/local-storage.service'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; vi.mock('app/drive/services/size.service', () => ({ bytesToString: vi.fn((bytes) => { @@ -513,4 +513,4 @@ describe('Testing GA Service', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/app/analytics/ga.service.ts b/src/app/analytics/ga.service.ts index 859af6679..83f89fd87 100644 --- a/src/app/analytics/ga.service.ts +++ b/src/app/analytics/ga.service.ts @@ -1,7 +1,7 @@ import { bytesToString } from 'app/drive/services/size.service'; import { formatPrice } from 'views/Checkout/utils/formatPrice'; import { getProductAmount } from 'views/Checkout/utils/getProductAmount'; -import { CouponCodeData } from 'views/Checkout/types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import envService from 'services/env.service'; import localStorageService from 'services/local-storage.service'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; @@ -138,7 +138,7 @@ function trackBeginCheckout(params: TrackBeginCheckoutParams): void { } } - function trackPurchase(): void { +function trackPurchase(): void { try { const userSettings = localStorageService.getUser() as UserSettings; if (!userSettings) { diff --git a/src/app/analytics/googleSheet.service.ts b/src/app/analytics/googleSheet.service.ts index b70d3de6d..2df7493a6 100644 --- a/src/app/analytics/googleSheet.service.ts +++ b/src/app/analytics/googleSheet.service.ts @@ -1,6 +1,6 @@ import { PriceWithTax } from '@internxt/sdk/dist/payments/types'; import envService from 'services/env.service'; -import { CouponCodeData } from 'views/Checkout/types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { getProductAmount } from 'views/Checkout/utils'; const WINTER_OFFSET_HOUR = 1; const TWO_EXTRA_HOURS_IN_MS = 2 * 60 * 60 * 1000; diff --git a/src/app/analytics/impact.service.ts b/src/app/analytics/impact.service.ts index a0ff1268a..db1059d61 100644 --- a/src/app/analytics/impact.service.ts +++ b/src/app/analytics/impact.service.ts @@ -7,7 +7,7 @@ import localStorageService from 'services/local-storage.service'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import envService from 'services/env.service'; import { PriceWithTax } from '@internxt/sdk/dist/payments/types'; -import { CouponCodeData } from 'views/Checkout/types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { bytesToString } from 'app/drive/services/size.service'; import { getProductAmount } from 'views/Checkout/utils'; import { sendAddShoppersConversion } from './addShoppers.services'; diff --git a/src/app/core/config/views.ts b/src/app/core/config/views.ts index 097ad624c..3e87543cc 100644 --- a/src/app/core/config/views.ts +++ b/src/app/core/config/views.ts @@ -21,13 +21,7 @@ import FolderFileNotFound from 'app/drive/views/FolderFileNotFound/FolderFileNot import RecentsView from 'views/Recents'; import RequestAccess from 'app/drive/views/RequestAccess/RequestAccess'; import TrashView from 'views/Trash'; -import { - CheckoutCancelView, - CheckoutSessionId, - CheckoutSuccessView, - CheckoutViewWrapper, - PcCloudSuccess, -} from 'views/Checkout'; +import { CheckoutCancelView, CheckoutSessionId, CheckoutSuccessView, CheckoutViewWrapper } from 'views/Checkout'; import { ShareFileView, ShareFolderView } from 'views/PublicShared'; import RedirectToAppView from '../../core/views/RedirectToAppView/RedirectToAppView'; import SharedViewWrapper from 'views/Shared/SharedViewWrapper'; @@ -58,7 +52,6 @@ const views: Array<{ { id: AppView.FolderFileNotFound, component: FolderFileNotFound }, { id: AppView.Deactivation, component: DeactivationView }, { id: AppView.CheckoutSuccess, component: CheckoutSuccessView }, - { id: AppView.PcCloudSuccess, component: PcCloudSuccess }, { id: AppView.CheckoutCancel, component: CheckoutCancelView }, { id: AppView.CheckoutSession, component: CheckoutSessionId }, { id: AppView.Checkout, component: CheckoutViewWrapper }, diff --git a/src/app/core/types.ts b/src/app/core/types.ts index f1f5384e3..b15c0cc6a 100644 --- a/src/app/core/types.ts +++ b/src/app/core/types.ts @@ -106,7 +106,6 @@ export enum AppView { FolderFileNotFound = 'folder-file-not-found', Deactivation = 'deactivation', CheckoutSuccess = 'checkout-success', - PcCloudSuccess = 'pcCloud-success', CheckoutCancel = 'checkout-cancel', Checkout = 'checkout', CheckoutSession = 'checkout-session', diff --git a/src/app/i18n/locales/de.json b/src/app/i18n/locales/de.json index 3140f6412..51810e253 100644 --- a/src/app/i18n/locales/de.json +++ b/src/app/i18n/locales/de.json @@ -280,7 +280,8 @@ "error": { "invalidPlan": "Nicht unterstützter Plantyp. Bitte wählen Sie einen gültigen Plan.", "fetchingCryptoCurrencies": "Die verfügbaren Kryptowährungsoptionen konnten nicht geladen werden", - "addressRequired": "Bitte vervollständigen Sie Ihre Rechnungsadresse, um fortzufahren" + "addressRequired": "Bitte vervollständigen Sie Ihre Rechnungsadresse, um fortzufahren", + "businessPlan": "Der Unternehmensplan ist nicht verfügbar" } }, "general": { diff --git a/src/app/i18n/locales/en.json b/src/app/i18n/locales/en.json index 3a9edafa4..3badc9361 100644 --- a/src/app/i18n/locales/en.json +++ b/src/app/i18n/locales/en.json @@ -328,7 +328,8 @@ "error": { "invalidPlan": "Unsupported plan type. Please select a valid plan.", "fetchingCryptoCurrencies": "We couldn't load the available cryptocurrency options", - "addressRequired": "Please complete your billing address to continue" + "addressRequired": "Please complete your billing address to continue", + "businessPlan": "Business plan is not available" } }, "general": { diff --git a/src/app/i18n/locales/es.json b/src/app/i18n/locales/es.json index 92c671e4a..39888dc62 100644 --- a/src/app/i18n/locales/es.json +++ b/src/app/i18n/locales/es.json @@ -307,7 +307,8 @@ "error": { "invalidPlan": "Tipo de plan no soportado. Por favor, selecciona un plan válido.", "fetchingCryptoCurrencies": "No pudimos cargar las opciones de criptomonedas disponibles", - "addressRequired": "Por favor, completa tu dirección de facturación para continuar" + "addressRequired": "Por favor, completa tu dirección de facturación para continuar", + "businessPlan": "El plan de empresa no está disponible" } }, "general": { diff --git a/src/app/i18n/locales/fr.json b/src/app/i18n/locales/fr.json index 2d65ff89d..5d7b2a334 100644 --- a/src/app/i18n/locales/fr.json +++ b/src/app/i18n/locales/fr.json @@ -280,7 +280,8 @@ "error": { "invalidPlan": "Type de plan non pris en charge. Veuillez sélectionner un plan valide.", "fetchingCryptoCurrencies": "Nous n'avons pas pu charger les options de crypto-monnaies disponibles", - "addressRequired": "Veuillez compléter votre adresse de facturation pour continuer" + "addressRequired": "Veuillez compléter votre adresse de facturation pour continuer", + "businessPlan": "Le plan d’entreprise n’est pas disponible" } }, "general": { diff --git a/src/app/i18n/locales/it.json b/src/app/i18n/locales/it.json index d254210ee..3eeb9b818 100644 --- a/src/app/i18n/locales/it.json +++ b/src/app/i18n/locales/it.json @@ -371,7 +371,8 @@ "error": { "invalidPlan": "Tipo di piano non supportato. Seleziona un piano valido.", "fetchingCryptoCurrencies": "Non siamo riusciti a caricare le opzioni di criptovalute disponibili", - "addressRequired": "Completa il tuo indirizzo di fatturazione per continuare" + "addressRequired": "Completa il tuo indirizzo di fatturazione per continuare", + "businessPlan": "Il piano aziendale non è disponibile" } }, "general": { diff --git a/src/app/i18n/locales/ru.json b/src/app/i18n/locales/ru.json index 4b0a98a06..8ce7f9e45 100644 --- a/src/app/i18n/locales/ru.json +++ b/src/app/i18n/locales/ru.json @@ -280,7 +280,8 @@ "error": { "invalidPlan": "Неподдерживаемый тип плана. Пожалуйста, выберите действительный план.", "fetchingCryptoCurrencies": "Не удалось загрузить доступные варианты криптовалют", - "addressRequired": "Пожалуйста, заполните адрес для выставления счета, чтобы продолжить" + "addressRequired": "Пожалуйста, заполните адрес для выставления счета, чтобы продолжить", + "businessPlan": "Бизнес-план недоступен" } }, "general": { diff --git a/src/app/i18n/locales/tw.json b/src/app/i18n/locales/tw.json index fbf8dbf57..de6ebf0f3 100644 --- a/src/app/i18n/locales/tw.json +++ b/src/app/i18n/locales/tw.json @@ -296,7 +296,8 @@ "error": { "invalidPlan": "不支援的方案類型。請選擇有效的方案。", "fetchingCryptoCurrencies": "無法載入可用的加密貨幣選項", - "addressRequired": "請填寫您的帳單地址以繼續" + "addressRequired": "請填寫您的帳單地址以繼續", + "businessPlan": "企業計劃不可用" } }, "general": { diff --git a/src/app/i18n/locales/zh.json b/src/app/i18n/locales/zh.json index 95dd06676..e4aecfa16 100644 --- a/src/app/i18n/locales/zh.json +++ b/src/app/i18n/locales/zh.json @@ -294,7 +294,8 @@ "error": { "invalidPlan": "不支持的套餐类型。请选择有效的套餐。", "fetchingCryptoCurrencies": "无法加载可用的加密货币选项", - "addressRequired": "请填写您的账单地址以继续" + "addressRequired": "请填写您的账单地址以继续", + "businessPlan": "企业计划不可用" } }, "general": { diff --git a/src/app/routes/paths.json b/src/app/routes/paths.json index c8b46e5eb..0d63b005e 100644 --- a/src/app/routes/paths.json +++ b/src/app/routes/paths.json @@ -141,13 +141,6 @@ "exact": false, "auth": true }, - { - "id": "pcCloud-success", - "layout": "empty", - "path": "/checkout/pcCloud-success", - "exact": false, - "auth": true - }, { "id": "checkout-cancel", "layout": "empty", diff --git a/src/app/store/index.ts b/src/app/store/index.ts index 76b3e1236..a7db3ce51 100644 --- a/src/app/store/index.ts +++ b/src/app/store/index.ts @@ -2,7 +2,6 @@ import { configureStore } from '@reduxjs/toolkit'; import backupsReducer from '../../views/Backups/store/backupsSlice'; import planReducer from './slices/plan'; -import productsReducer from './slices/products'; import referralsReducer from './slices/referrals'; import sessionReducer from './slices/session'; import sharedReducer from './slices/sharedLinks'; @@ -20,7 +19,6 @@ export const store = configureStore({ session: sessionReducer, ui: uiReducer, plan: planReducer, - products: productsReducer, backups: backupsReducer, taskManager: taskManagerReducer, referrals: referralsReducer, diff --git a/src/app/store/slices/products/index.ts b/src/app/store/slices/products/index.ts deleted file mode 100644 index da83ce5fd..000000000 --- a/src/app/store/slices/products/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; - -import { RootState } from '../..'; -import { fetchProducts } from 'views/Checkout/services'; -import { ProductData, RenewalPeriod } from 'views/Checkout/types'; - -interface ProductsState { - isLoading: boolean; - allProducts: ProductData[]; -} - -const initialState: ProductsState = { - isLoading: false, - allProducts: [], -}; - -export const initializeThunk = createAsyncThunk( - 'products/initialize', - async (payload: void, { dispatch, getState }) => { - const isAuthenticated = getState().user.isAuthenticated; - - if (isAuthenticated) { - await dispatch(fetchProductsThunk()); - } - }, -); - -export const fetchProductsThunk = createAsyncThunk<{ products: ProductData[] }, void, { state: RootState }>( - 'products/fetchProducts', - async () => { - const products = await fetchProducts(); - - return { products }; - }, -); - -export const productsSlice = createSlice({ - name: 'products', - initialState, - reducers: {}, - extraReducers: (builder) => { - builder - .addCase(initializeThunk.pending, () => undefined) - .addCase(initializeThunk.fulfilled, () => undefined) - .addCase(initializeThunk.rejected, () => undefined); - - builder - .addCase(fetchProductsThunk.pending, (state) => { - state.isLoading = true; - }) - .addCase(fetchProductsThunk.fulfilled, (state, action) => { - state.isLoading = false; - state.allProducts = action.payload.products; - }) - .addCase(fetchProductsThunk.rejected, (state) => { - state.isLoading = false; - }); - }, -}); - -export const productsActions = productsSlice.actions; - -export const productsThunks = { - initializeThunk, - fetchProductsThunk, -}; - -export const productsSelectors = { - individualProducts(state: RootState): (renewalPeriod: RenewalPeriod) => ProductData[] { - return (renewalPeriod) => - state.products.allProducts.filter( - (product) => product.metadata.is_drive && product.renewalPeriod === renewalPeriod, - ); - }, - teamProducts(state: RootState): (renewalPeriod: RenewalPeriod) => ProductData[] { - return (renewalPeriod) => - state.products.allProducts.filter( - (product) => product.metadata.is_teams && product.renewalPeriod === renewalPeriod, - ); - }, -}; - -export default productsSlice.reducer; diff --git a/src/services/auth.service.test.ts b/src/services/auth.service.test.ts index 253a7457d..eed037448 100644 --- a/src/services/auth.service.test.ts +++ b/src/services/auth.service.test.ts @@ -61,11 +61,6 @@ beforeAll(() => { initializeThunk: vi.fn(), }, })); - vi.mock('app/store/slices/products', () => ({ - productsThunks: { - initializeThunk: vi.fn(), - }, - })); vi.mock('app/store/slices/referrals', () => ({ referralsThunks: { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 3e42940f3..305ba47bf 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -32,7 +32,6 @@ import { import databaseService from 'app/database/services/database.service'; import { AppDispatch } from 'app/store'; import { planThunks } from 'app/store/slices/plan'; -import { productsThunks } from 'app/store/slices/products'; import { initializeUserThunk, userActions, userThunks } from 'app/store/slices/user'; import { workspaceThunks } from 'app/store/slices/workspaces/workspacesStore'; import { generateMnemonic, validateMnemonic } from 'bip39'; @@ -559,7 +558,7 @@ export const signUp = async (params: SignUpParams) => { localStorageService.clear(); localStorageService.set(LocalStorageItem.UserToken, xToken); - localStorageService.set(LocalStorageItem.UserMnemonic , mnemonic); + localStorageService.set(LocalStorageItem.UserMnemonic, mnemonic); localStorageService.set(LocalStorageItem.NewToken, xNewToken); const { publicKey, privateKey, publicKyberKey, privateKyberKey } = parseAndDecryptUserKeys(xUser, password); @@ -581,7 +580,6 @@ export const signUp = async (params: SignUpParams) => { dispatch(userActions.setUser(user)); await dispatch(userThunks.initializeUserThunk()); - dispatch(productsThunks.initializeThunk()); if (!redeemCodeObject) dispatch(planThunks.initializeThunk()); await trackSignUp(xUser.uuid); @@ -596,7 +594,6 @@ export const logIn = async (params: LogInParams): Promise => { dispatch(userActions.setUser(user)); try { - dispatch(productsThunks.initializeThunk()); dispatch(planThunks.initializeThunk()); await dispatch(initializeUserThunk())?.unwrap(); dispatch(workspaceThunks.fetchWorkspaces()); diff --git a/src/services/error.service.test.ts b/src/services/error.service.test.ts index c02752a81..553fd70f3 100644 --- a/src/services/error.service.test.ts +++ b/src/services/error.service.test.ts @@ -29,7 +29,7 @@ describe('Error Service', () => { headers: { 'x-request-id': xRequestId }, config: {} as never, } as AxiosResponse; - return new AxiosResponseError('Request failed', 'POST /auth/login', response); + return new AxiosResponseError('Request failed', 'POST /auth/login', response as never); }; const createAxiosError = (data: unknown, status?: number, headers?: Record): AxiosError => { diff --git a/src/views/Checkout/components/CheckoutProductCard.tsx b/src/views/Checkout/components/CheckoutProductCard.tsx index 74e006e26..4297b7ce8 100644 --- a/src/views/Checkout/components/CheckoutProductCard.tsx +++ b/src/views/Checkout/components/CheckoutProductCard.tsx @@ -1,5 +1,5 @@ import { Transition } from '@headlessui/react'; -import { UserType } from '@internxt/sdk/dist/drive/payments/types/types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { Check, SealPercent, X } from '@phosphor-icons/react'; import { useState } from 'react'; @@ -13,15 +13,12 @@ import { bytesToString } from 'app/drive/services/size.service'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; import TextInput from 'components/TextInput'; import { useThemeContext } from 'app/theme/ThemeProvider'; -import { CouponCodeData, Currency } from '../types'; -import { SelectSeatsComponent } from './SelectSeatsComponent'; +import { Currency } from '../types'; interface CheckoutProductCardProps { selectedPlan: PriceWithTax; - seatsForBusinessSubscription: number; showCouponCode: boolean; showHardcodedRenewal?: string; - onSeatsChange: (users: number) => void; onRemoveAppliedCouponCode: () => void; onCouponInputChange: (promoCode?: string) => void; couponCodeData?: CouponCodeData; @@ -36,8 +33,6 @@ export const CheckoutProductCard = ({ showCouponCode, showHardcodedRenewal, couponError, - seatsForBusinessSubscription, - onSeatsChange, onRemoveAppliedCouponCode, onCouponInputChange, }: CheckoutProductCardProps) => { @@ -58,13 +53,7 @@ export const CheckoutProductCard = ({ const currencySymbol = Currency[priceData.currency]; const normalPriceAmount = priceData.decimalAmount; - const isBusiness = priceData.type === UserType.Business; - const perUserLabel = isBusiness ? translate('checkout.productCard.perUser') : undefined; - const totalLabel = isBusiness - ? translate('checkout.productCard.totalForBusiness', { - N: seatsForBusinessSubscription, - }) - : translate('checkout.productCard.total'); + const totalLabel = translate('checkout.productCard.total'); const renewalPeriodLabel = `${translate('checkout.productCard.renewalPeriod.renewsAt')} ${currencySymbol}${normalPriceAmount}/${translate( `checkout.productCard.renewalPeriod.${priceData.interval}`, @@ -77,7 +66,7 @@ export const CheckoutProductCard = ({ ? ((couponCodeData?.amountOff / taxesData.amountWithTax) * 100).toFixed(2) : undefined; - const planType = isBusiness ? 'businessPlanFeaturesList' : 'planFeaturesList'; + const planType = 'planFeaturesList'; const productLabel = translate(`preferences.account.plans.${planType}.${bytes}.title`) ?? bytes; const featureKeys = @@ -104,26 +93,8 @@ export const CheckoutProductCard = ({

{productLabel + ' - ' + translate(`checkout.productCard.renewalTitle.${priceData.interval}`)}

- {isBusiness && priceData?.maximumSeats && priceData?.minimumSeats ? ( - <> -

- {translate('checkout.productCard.numberOfUsers', { - seats: seatsForBusinessSubscription, - })} -

- - - ) : undefined}
-

- {translate(`checkout.productCard.billed.${priceData.interval}`)} - {perUserLabel} -

+

{translate(`checkout.productCard.billed.${priceData.interval}`)}

{currencySymbol} {planAmountWithoutTaxes} @@ -184,7 +155,7 @@ export const CheckoutProductCard = ({

{totalLabel}

{currencySymbol} - {formatPrice(taxesData.decimalAmountWithTax * seatsForBusinessSubscription)} + {formatPrice(taxesData.decimalAmountWithTax)}

diff --git a/src/views/Checkout/hooks/useCheckout.test.ts b/src/views/Checkout/hooks/useCheckout.test.ts index f2cbb85d2..351186474 100644 --- a/src/views/Checkout/hooks/useCheckout.test.ts +++ b/src/views/Checkout/hooks/useCheckout.test.ts @@ -15,7 +15,6 @@ describe('useCheckout hook actions', () => { setPlan, setSelectedPlan, setStripeElementsOptions, - setSeatsForBusinessSubscription, setIsCheckoutReadyToRender, setIsUpdateSubscriptionDialogOpen, setIsUpdatingSubscription, @@ -72,11 +71,6 @@ describe('useCheckout hook actions', () => { expect(dispatch).toHaveBeenCalledWith({ type: 'SET_ELEMENTS_OPTIONS', payload: options }); }); - it('When setSeatsForBusinessSubscription is called, then it dispatches SET_SEATS_FOR_BUSINESS_SUBSCRIPTION with the given seat count', () => { - setSeatsForBusinessSubscription(5); - expect(dispatch).toHaveBeenCalledWith({ type: 'SET_SEATS_FOR_BUSINESS_SUBSCRIPTION', payload: 5 }); - }); - it('When setIsCheckoutReadyToRender is called, then it dispatches SET_IS_CHECKOUT_READY_TO_RENDER with the boolean value', () => { setIsCheckoutReadyToRender(true); expect(dispatch).toHaveBeenCalledWith({ type: 'SET_IS_CHECKOUT_READY_TO_RENDER', payload: true }); diff --git a/src/views/Checkout/hooks/useCheckout.ts b/src/views/Checkout/hooks/useCheckout.ts index f999f9bcc..5f28f58b2 100644 --- a/src/views/Checkout/hooks/useCheckout.ts +++ b/src/views/Checkout/hooks/useCheckout.ts @@ -1,8 +1,8 @@ import { Dispatch } from 'react'; import { Action } from '../store'; -import { AuthMethodTypes, CouponCodeData, ErrorType } from '../types'; +import { AuthMethodTypes, ErrorType } from '../types'; +import { CouponCodeData, DisplayPrice } from '@internxt/sdk/dist/drive/payments/types/types'; import { StripeElementsOptions } from '@stripe/stripe-js'; -import { DisplayPrice } from '@internxt/sdk/dist/drive/payments/types/types'; import { PriceWithTax } from '@internxt/sdk/dist/payments/types'; export const useCheckout = (dispatchReducer: Dispatch) => { @@ -36,10 +36,6 @@ export const useCheckout = (dispatchReducer: Dispatch) => { dispatchReducer({ type: 'SET_PLAN', payload: plan }); }; - const setSeatsForBusinessSubscription = (seats: number) => { - dispatchReducer({ type: 'SET_SEATS_FOR_BUSINESS_SUBSCRIPTION', payload: seats }); - }; - const setSelectedPlan = (selectedPlan: PriceWithTax) => { dispatchReducer({ type: 'SET_CURRENT_PLAN_SELECTED', payload: selectedPlan }); }; @@ -95,7 +91,6 @@ export const useCheckout = (dispatchReducer: Dispatch) => { setPromoCodeData, setSelectedPlan, setStripeElementsOptions, - setSeatsForBusinessSubscription, setPrices, setIsCheckoutReadyToRender, setIsUpdateSubscriptionDialogOpen, diff --git a/src/views/Checkout/hooks/useInitializeCheckout.ts b/src/views/Checkout/hooks/useInitializeCheckout.ts index ba99c625e..2c6442ca2 100644 --- a/src/views/Checkout/hooks/useInitializeCheckout.ts +++ b/src/views/Checkout/hooks/useInitializeCheckout.ts @@ -8,6 +8,7 @@ import { CryptoCurrency, PriceWithTax } from '@internxt/sdk/dist/payments/types' import { IS_CRYPTO_PAYMENT_ENABLED, THEME_STYLES } from '../constants'; import { PlanInterval } from '../types'; import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; +import { UserType } from '@internxt/sdk/dist/drive/payments/types/types'; interface UseInitializeCheckoutProps { checkoutTheme: string; @@ -27,6 +28,14 @@ export const useInitializeCheckout = ({ user, price, checkoutTheme, translate }: }, []); useEffect(() => { + if (price?.price.type === UserType.Business) { + notificationsService.show({ + text: translate('checkout.error.businessPlan'), + type: ToastType.Warning, + }); + return redirectToFallbackPage(); + } + if (stripeSdk && price) { loadStripeAndCrypto(); } diff --git a/src/views/Checkout/hooks/useProducts.test.ts b/src/views/Checkout/hooks/useProducts.test.ts index 8a6f25d67..7bed1bc94 100644 --- a/src/views/Checkout/hooks/useProducts.test.ts +++ b/src/views/Checkout/hooks/useProducts.test.ts @@ -18,7 +18,6 @@ describe('Products custom hook', () => { amount: 10, interval: 'year', type: UserType.Individual, - minimumSeats: undefined, }, taxes: { amountWithTax: 1210, @@ -128,31 +127,6 @@ describe('Products custom hook', () => { expect((result.current.selectedPlan as any)?.decimalAmount).toBe(0); }); - test('When fetching a plan returns a plan with seats, then the they are updated', async () => { - const planWithMinimumSeats = { - ...mockPriceWithTax, - price: { - ...mockPriceWithTax.price, - minimumSeats: 5, - }, - }; - vi.spyOn(checkoutService, 'getPriceById').mockResolvedValue(planWithMinimumSeats); - const props = { - planId: null, - promotionCode: undefined, - currency: 'eur', - translate: mockTranslate, - }; - - const { result } = renderHook(() => useProducts(props)); - - await act(async () => { - await result.current.fetchSelectedPlan({ priceId: 'price_business', currency: 'eur' }); - }); - - expect(result.current.businessSeats).toBe(5); - }); - test('When fetching a plan without currency, then eur is used as default', async () => { vi.spyOn(checkoutService, 'getPriceById').mockResolvedValue(mockPriceWithTax); const props = { @@ -187,7 +161,6 @@ describe('Products custom hook', () => { const { result } = renderHook(() => useProducts(props)); expect(result.current).toHaveProperty('selectedPlan'); - expect(result.current).toHaveProperty('businessSeats'); expect(result.current).toHaveProperty('fetchSelectedPlan'); expect(typeof result.current.fetchSelectedPlan).toBe('function'); }); diff --git a/src/views/Checkout/hooks/useProducts.ts b/src/views/Checkout/hooks/useProducts.ts index 4df4f54fe..5630d8eca 100644 --- a/src/views/Checkout/hooks/useProducts.ts +++ b/src/views/Checkout/hooks/useProducts.ts @@ -26,7 +26,6 @@ interface FetchSelectedPlanPayload { export const useProducts = ({ currency, planId, promotionCode, userLocation, userAddress }: UseProductsProps) => { const [selectedPlan, setSelectedPlan] = useState(); - const [businessSeats, setBusinessSeats] = useState(1); useEffect(() => { if (!planId || !userAddress) return; @@ -56,16 +55,12 @@ export const useProducts = ({ currency, planId, promotionCode, userLocation, use const amount = mobileToken ? { amount: 0, decimalAmount: 0 } : {}; setSelectedPlan({ ...plan, ...amount }); - if (plan?.price?.minimumSeats) { - setBusinessSeats(plan.price.minimumSeats); - } return plan; }; return { selectedPlan, - businessSeats, fetchSelectedPlan, }; }; diff --git a/src/views/Checkout/hooks/usePromotionalCode.ts b/src/views/Checkout/hooks/usePromotionalCode.ts index aa9ebed38..16d6dc9de 100644 --- a/src/views/Checkout/hooks/usePromotionalCode.ts +++ b/src/views/Checkout/hooks/usePromotionalCode.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; import { checkoutService } from '../services'; -import { CouponCodeData } from '../types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { STATUS_CODE_ERROR } from '../constants'; import { errorService } from 'services'; diff --git a/src/views/Checkout/hooks/useUserPayment.test.ts b/src/views/Checkout/hooks/useUserPayment.test.ts index e94c7fe6b..61a605529 100644 --- a/src/views/Checkout/hooks/useUserPayment.test.ts +++ b/src/views/Checkout/hooks/useUserPayment.test.ts @@ -48,7 +48,6 @@ describe('Custom hook to handle payments', () => { currency: 'eur', captchaToken: 'captcha_token', promoCodeId: 'promo_code_id', - quantity: 1, }; const response = await getSubscriptionPaymentIntent(subscriptionPaymentIntentPayload); @@ -60,7 +59,6 @@ describe('Custom hook to handle payments', () => { currency: subscriptionPaymentIntentPayload.currency, promoCodeId: subscriptionPaymentIntentPayload.promoCodeId, captchaToken: subscriptionPaymentIntentPayload.captchaToken, - quantity: subscriptionPaymentIntentPayload.quantity, }); expect(response).toStrictEqual({ type: 'payment', @@ -167,7 +165,6 @@ describe('Custom hook to handle payments', () => { priceId: 'price_id', token: 'token', currency: 'currency', - seatsForBusinessSubscription: 1, elements: vi.fn() as any, currentSelectedPlan: { price: { @@ -190,7 +187,6 @@ describe('Custom hook to handle payments', () => { token: subscriptionPaymentPayload.token, currency: subscriptionPaymentPayload.currency, promoCodeId: undefined, - quantity: subscriptionPaymentPayload.seatsForBusinessSubscription, captchaToken: subscriptionPaymentPayload.captchaToken, }); @@ -225,7 +221,6 @@ describe('Custom hook to handle payments', () => { priceId: 'price_id', token: 'token', currency: 'currency', - seatsForBusinessSubscription: 1, elements: vi.fn() as any, currentSelectedPlan: { price: { @@ -248,7 +243,6 @@ describe('Custom hook to handle payments', () => { token: subscriptionPaymentPayload.token, currency: subscriptionPaymentPayload.currency, promoCodeId: undefined, - quantity: subscriptionPaymentPayload.seatsForBusinessSubscription, captchaToken: subscriptionPaymentPayload.captchaToken, }); @@ -284,7 +278,6 @@ describe('Custom hook to handle payments', () => { priceId: 'price_id', token: 'token', currency: 'currency', - seatsForBusinessSubscription: 1, elements: vi.fn() as any, currentSelectedPlan: { price: { @@ -307,7 +300,6 @@ describe('Custom hook to handle payments', () => { token: subscriptionPaymentPayload.token, currency: subscriptionPaymentPayload.currency, promoCodeId: undefined, - quantity: subscriptionPaymentPayload.seatsForBusinessSubscription, captchaToken: subscriptionPaymentPayload.captchaToken, }); @@ -472,7 +464,6 @@ describe('Custom hook to handle payments', () => { token: 'token', translate: vi.fn(), currency: 'currency', - seatsForBusinessSubscription: 1, elements: { create: vi.fn(), fetchUpdates: vi.fn(), @@ -520,7 +511,6 @@ describe('Custom hook to handle payments', () => { token: 'token', translate: vi.fn(), currency: 'currency', - seatsForBusinessSubscription: 1, elements: { create: vi.fn(), fetchUpdates: vi.fn(), @@ -564,7 +554,6 @@ describe('Custom hook to handle payments', () => { token: 'token', translate: vi.fn(), currency: 'currency', - seatsForBusinessSubscription: 1, elements: { create: vi.fn(), fetchUpdates: vi.fn(), diff --git a/src/views/Checkout/hooks/useUserPayment.ts b/src/views/Checkout/hooks/useUserPayment.ts index 8fbf82274..f32560076 100644 --- a/src/views/Checkout/hooks/useUserPayment.ts +++ b/src/views/Checkout/hooks/useUserPayment.ts @@ -38,7 +38,6 @@ export const useUserPayment = () => { priceId, token, currency, - quantity, captchaToken, promoCodeId, }: CreateSubscriptionPayload) => { @@ -54,7 +53,6 @@ export const useUserPayment = () => { currency, captchaToken, promoCodeId, - quantity, }); return { @@ -146,7 +144,6 @@ export const useUserPayment = () => { priceId, token, currency, - seatsForBusinessSubscription = 1, currentSelectedPlan, couponCodeData, elements, @@ -160,7 +157,6 @@ export const useUserPayment = () => { customerId, priceId, token, - quantity: seatsForBusinessSubscription, captchaToken, promoCodeId: couponCodeData?.codeId, currency, @@ -170,7 +166,7 @@ export const useUserPayment = () => { subscriptionId: subscription.subscriptionId, paymentIntentId: subscription.paymentIntentId, selectedPlan: currentSelectedPlan, - users: seatsForBusinessSubscription, + users: 1, couponCodeData, isFirstPurchase: isFirstPurchase ?? false, }); @@ -272,7 +268,6 @@ export const useUserPayment = () => { couponCodeData, elements, gclidStored, - seatsForBusinessSubscription = 1, captchaToken, userAddress, translate, @@ -290,7 +285,7 @@ export const useUserPayment = () => { value: selectedPlan, currency, timestamp: new Date(), - users: seatsForBusinessSubscription, + users: 1, couponCodeData: couponCodeData, }); } @@ -306,7 +301,6 @@ export const useUserPayment = () => { priceId, token, couponCodeData, - seatsForBusinessSubscription, captchaToken, userAddress, translate, diff --git a/src/views/Checkout/services/checkout.service.test.ts b/src/views/Checkout/services/checkout.service.test.ts index 2b2252fb7..30e02b1e9 100644 --- a/src/views/Checkout/services/checkout.service.test.ts +++ b/src/views/Checkout/services/checkout.service.test.ts @@ -8,12 +8,6 @@ import { } from '@internxt/sdk/dist/payments/types'; import userService from 'services/user.service'; -vi.mock('./payment.service', () => ({ - default: { - createSubscriptionWithTrial: vi.fn(), - }, -})); - vi.mock('app/drive/services/size.service', () => ({ bytesToString: (bytes: number) => `${bytes} B`, })); @@ -192,7 +186,6 @@ describe('Checkout Service tests', () => { customerId: 'cus_123', priceId: 'price_123', token: 'user_mocked_token', - quantity: 3, captchaToken: 'captcha_token', }; diff --git a/src/views/Checkout/services/checkout.service.ts b/src/views/Checkout/services/checkout.service.ts index 4ed531cde..78b2c83d5 100644 --- a/src/views/Checkout/services/checkout.service.ts +++ b/src/views/Checkout/services/checkout.service.ts @@ -1,5 +1,9 @@ -import { CreatedSubscriptionData, DisplayPrice, UserType } from '@internxt/sdk/dist/drive/payments/types/types'; -import { CouponCodeData } from '../types'; +import { + CouponCodeData, + CreatedSubscriptionData, + DisplayPrice, + UserType, +} from '@internxt/sdk/dist/drive/payments/types/types'; import axios from 'axios'; import localStorageService from 'services/local-storage.service'; import { SdkFactory } from 'app/core/factory/sdk'; @@ -21,24 +25,9 @@ import { LocalStorageItem } from 'app/core/types'; const BORDER_SHADOW = 'rgb(0 102 255)'; const fetchPromotionCodeByName = async (priceId: string, promotionCodeName: string): Promise => { - const PAYMENTS_API_URL = envService.getVariable('payments'); - const response = await fetch( - `${PAYMENTS_API_URL}/promo-code-by-name?priceId=${priceId}&promotionCode=${promotionCodeName}`, - ); + const paymentClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - if (response.status !== 200) { - const message = await response.text(); - throw new Error(message); - } - - const dataJson = await response.json(); - - return { - codeId: dataJson.codeId, - codeName: promotionCodeName, - amountOff: dataJson.amountOff, - percentOff: dataJson.percentOff, - }; + return paymentClient.fetchPromotionCodeByName(priceId, promotionCodeName); }; const createCustomer = async ({ @@ -95,7 +84,6 @@ const createSubscription = async ({ currency, captchaToken, promoCodeId, - quantity, }: CreateSubscriptionPayload): Promise => { const checkoutClient = await SdkFactory.getNewApiInstance().createCheckoutClient(); return checkoutClient.createSubscription({ @@ -105,7 +93,6 @@ const createSubscription = async ({ currency, captchaToken, promoCodeId, - quantity, }); }; diff --git a/src/views/Checkout/services/index.ts b/src/views/Checkout/services/index.ts index 19b8d150b..fbf91b7db 100644 --- a/src/views/Checkout/services/index.ts +++ b/src/views/Checkout/services/index.ts @@ -1,4 +1,4 @@ export { default as checkoutService } from './checkout.service'; export { default as currencyService } from './currency.service'; export { default as paymentService } from './payment.service'; -export { fetchProducts, ProductService, type UserTierFeatures } from './products.service'; +export { ProductService, type UserTierFeatures } from './products.service'; diff --git a/src/views/Checkout/services/payment.service.test.ts b/src/views/Checkout/services/payment.service.test.ts index 569c4351d..10a27408d 100644 --- a/src/views/Checkout/services/payment.service.test.ts +++ b/src/views/Checkout/services/payment.service.test.ts @@ -2,10 +2,8 @@ import { describe, it, expect, vi, beforeEach, test } from 'vitest'; import { loadStripe } from '@stripe/stripe-js/pure'; import paymentService from './payment.service'; import { SdkFactory } from '../../../app/core/factory/sdk'; -import { envService, localStorageService } from 'services'; -import axios from 'axios'; +import { envService } from 'services'; import { UserType } from '@internxt/sdk/dist/drive/payments/types/types'; -import { StripeSessionMode } from '../types'; vi.mock('@stripe/stripe-js/pure'); vi.mock('axios'); @@ -57,69 +55,6 @@ describe('paymentService', () => { }); }); - describe('getCustomerId', () => { - it('creates customer account and receives credentials', async () => { - const mockResponse = { customerId: 'cus_123', token: 'tok_123' }; - mockPaymentsClient.createCustomer.mockResolvedValue(mockResponse); - - const result = await paymentService.getCustomerId('John Doe', 'john@example.com', 'US', 'VAT123'); - - expect(mockPaymentsClient.createCustomer).toHaveBeenCalledWith('John Doe', 'john@example.com', 'US', 'VAT123'); - expect(result).toEqual(mockResponse); - }); - }); - - describe('createSubscription', () => { - it('sets up recurring subscription with discount and team size', async () => { - const mockResponse = { clientSecret: 'cs_123', subscriptionId: 'sub_123' }; - mockPaymentsClient.createSubscription.mockResolvedValue(mockResponse); - - const result = await paymentService.createSubscription('cus_123', 'price_123', 'tok_123', 'usd', 'PROMO10', 2); - - expect(mockPaymentsClient.createSubscription).toHaveBeenCalledWith( - 'cus_123', - 'price_123', - 'tok_123', - 2, - 'usd', - 'PROMO10', - ); - expect(result).toEqual(mockResponse); - }); - }); - - describe('createPaymentIntent', () => { - it('initiates one-time payment for selected plan', async () => { - const mockResponse = { clientSecret: 'cs_123', id: 'pi_123' }; - mockPaymentsClient.createPaymentIntent.mockResolvedValue(mockResponse); - - const result = await paymentService.createPaymentIntent('cus_123', 1000, 'plan_123', 'tok_123', 'usd', 'PROMO'); - - expect(mockPaymentsClient.createPaymentIntent).toHaveBeenCalledWith( - 'cus_123', - 1000, - 'plan_123', - 'tok_123', - 'usd', - 'PROMO', - ); - expect(result).toEqual(mockResponse); - }); - }); - - describe('createSession', () => { - it('launches payment page for user', async () => { - const payload = { mode: 'subscription' as StripeSessionMode, priceId: 'price_123' }; - const mockResponse = { id: 'cs_123' }; - mockPaymentsClient.createSession.mockResolvedValue(mockResponse); - - const result = await paymentService.createSession(payload); - - expect(mockPaymentsClient.createSession).toHaveBeenCalledWith(payload); - expect(result).toEqual(mockResponse); - }); - }); - describe('updateSubscriptionPrice', () => { it('upgrades or downgrades plan with coupon', async () => { mockPaymentsClient.updateSubscriptionPrice.mockResolvedValue({ @@ -211,17 +146,6 @@ describe('paymentService', () => { }); }); - describe('requestPreventCancellation', () => { - it('checks if user can receive special offer to stay', async () => { - mockPaymentsClient.requestPreventCancellation.mockResolvedValue({ elegible: true }); - - const result = await paymentService.requestPreventCancellation(); - - expect(mockPaymentsClient.requestPreventCancellation).toHaveBeenCalled(); - expect(result.elegible).toBe(true); - }); - }); - describe('getUserSubscription', () => { it('gets current subscription information for personal account', async () => { const mockSubscription = { @@ -276,47 +200,4 @@ describe('paymentService', () => { expect(result).toEqual(mockPrices); }); }); - - describe('isCouponUsedByUser', () => { - it('verifies if user previously used this discount code', async () => { - const couponCode = 'SAVE20'; - mockPaymentsClient.isCouponUsedByUser.mockResolvedValue({ couponUsed: true }); - - const result = await paymentService.isCouponUsedByUser(couponCode); - - expect(mockPaymentsClient.isCouponUsedByUser).toHaveBeenCalledWith({ couponCode }); - expect(result).toEqual({ couponUsed: true }); - }); - - it('confirms discount code is available for user', async () => { - const couponCode = 'DISCOUNT10'; - mockPaymentsClient.isCouponUsedByUser.mockResolvedValue({ couponUsed: false }); - - const result = await paymentService.isCouponUsedByUser(couponCode); - - expect(mockPaymentsClient.isCouponUsedByUser).toHaveBeenCalledWith({ couponCode: 'DISCOUNT10' }); - expect(result).toEqual({ couponUsed: false }); - }); - }); - describe('createSubscriptionWithTrial', () => { - it('starts subscription with complimentary trial', async () => { - vi.spyOn(localStorageService, 'get').mockReturnValue('token'); - vi.spyOn(envService, 'getVariable').mockReturnValue('https://api.test.com'); - vi.mocked(axios.post).mockResolvedValue({ data: { clientSecret: 'cs_123' } }); - - await paymentService.createSubscriptionWithTrial('cus_123', 'price_123', 'tok_123', 'trial_tok', 'usd'); - - expect(axios.post).toHaveBeenCalledWith( - 'https://api.test.com/create-subscription-with-trial?trialToken=trial_tok', - { customerId: 'cus_123', priceId: 'price_123', currency: 'usd', token: 'tok_123' }, - { headers: { Authorization: 'Bearer token', 'Content-Type': 'application/json' } }, - ); - }); - - it('prevents trial activation without valid login', async () => { - vi.spyOn(localStorageService, 'get').mockReturnValue(null); - - await expect(paymentService.createSubscriptionWithTrial('cus', 'price', 'tok', 'trial')).rejects.toThrow(); - }); - }); }); diff --git a/src/views/Checkout/services/payment.service.ts b/src/views/Checkout/services/payment.service.ts index 6d8237b16..d05da7d1f 100644 --- a/src/views/Checkout/services/payment.service.ts +++ b/src/views/Checkout/services/payment.service.ts @@ -1,8 +1,6 @@ import { - CreatedSubscriptionData, CustomerBillingInfo, DisplayPrice, - FreeTrialAvailable, Invoice, InvoicePayload, PaymentMethod, @@ -12,13 +10,9 @@ import { } from '@internxt/sdk/dist/drive/payments/types/types'; import { RedirectToCheckoutServerOptions, Source, Stripe, StripeError } from '@stripe/stripe-js'; import { loadStripe } from '@stripe/stripe-js/pure'; -import axios from 'axios'; import { SdkFactory } from 'app/core/factory/sdk'; import envService from 'services/env.service'; -import localStorageService from 'services/local-storage.service'; -import errorService from 'services/error.service'; import { LifetimeTier, StripeSessionMode } from '../types'; -import { LocalStorageItem } from 'app/core/types'; export interface CreatePaymentSessionPayload { test?: boolean; @@ -44,45 +38,6 @@ const paymentService = { return stripe; }, - async getCustomerId( - name: string, - email: string, - country?: string, - companyVatId?: string, - ): Promise<{ customerId: string; token: string }> { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.createCustomer(name, email, country, companyVatId); - }, - - async createSubscription( - customerId: string, - priceId: string, - token: string, - currency: string, - promoCode?: string, - seats = 1, - ): Promise { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.createSubscription(customerId, priceId, token, seats, currency, promoCode); - }, - - async createPaymentIntent( - customerId: string, - amount: number, - planId: string, - token: string, - currency?: string, - promoCode?: string, - ): Promise<{ clientSecret: string; id: string; invoiceStatus?: string }> { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.createPaymentIntent(customerId, amount, planId, token, currency, promoCode); - }, - - async createSession(payload: CreatePaymentSessionPayload): Promise<{ id: string }> { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.createSession(payload); - }, - async createSetupIntent(userType?: UserType): Promise<{ clientSecret: string }> { const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); return paymentsClient.getSetupIntent(userType); @@ -114,13 +69,6 @@ const paymentService = { return paymentsClient.getPrices(currency, userType); }, - async isCouponUsedByUser(couponCode: string): Promise<{ - couponUsed: boolean; - }> { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.isCouponUsedByUser({ couponCode: couponCode }); - }, - async getPromoCodesUsedByUser(): Promise<{ usedCoupons: string[]; }> { @@ -128,16 +76,6 @@ const paymentService = { return paymentsClient.getPromoCodesUsedByUser(); }, - async requestPreventCancellation(): Promise { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.requestPreventCancellation(); - }, - - async preventCancellation(): Promise { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.preventCancellation(); - }, - async redeemCode(payload: RedeemCodePayload): Promise { const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); return paymentsClient.applyRedeemCode({ @@ -203,43 +141,6 @@ const paymentService = { const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); return paymentsClient.updateCustomerBillingInfo(payload); }, - - async createSubscriptionWithTrial( - customerId: string, - priceId: string, - token: string, - mobileToken: string, - currency?: string, - ): Promise { - try { - const newToken = localStorageService.get(LocalStorageItem.NewToken); - - if (!newToken) { - throw new Error('No authentication token available'); - } - const PAYMENTS_API_URL = envService.getVariable('payments'); - const response = await axios.post( - `${PAYMENTS_API_URL}/create-subscription-with-trial?trialToken=${mobileToken}`, - { - customerId, - priceId, - currency, - token, - }, - { - headers: { - Authorization: `Bearer ${newToken}`, - 'Content-Type': 'application/json', - }, - }, - ); - - return response.data; - } catch (error) { - errorService.reportError(error); - throw new Error('Error creating subscription with trial'); - } - }, }; export default paymentService; diff --git a/src/views/Checkout/services/products.service.ts b/src/views/Checkout/services/products.service.ts index e63ee65ad..4b16b594e 100644 --- a/src/views/Checkout/services/products.service.ts +++ b/src/views/Checkout/services/products.service.ts @@ -1,15 +1,8 @@ -import { ProductData } from '../types'; import { SdkFactory } from 'app/core/factory/sdk'; import { Tier } from '@internxt/sdk/dist/drive/payments/types/tiers'; export type UserTierFeatures = Tier['featuresPerService']; -// TODO: REMOVE THIS USELESS CALL -export const fetchProducts = async (): Promise => { - const paymentsClient = await SdkFactory.getNewApiInstance().createPaymentsClient(); - return paymentsClient.getProducts() as unknown as ProductData[]; -}; - export class ProductService { public static readonly instance: ProductService = new ProductService(); diff --git a/src/views/Checkout/store/checkoutReducer.test.ts b/src/views/Checkout/store/checkoutReducer.test.ts index c1f302330..65589a1db 100644 --- a/src/views/Checkout/store/checkoutReducer.test.ts +++ b/src/views/Checkout/store/checkoutReducer.test.ts @@ -2,9 +2,9 @@ import { describe, it, expect } from 'vitest'; import { checkoutReducer, initialStateForCheckout } from './checkoutReducer'; import { State } from './types'; import { PriceWithTax } from '@internxt/sdk/dist/payments/types'; -import { DisplayPrice, UserType } from '@internxt/sdk/dist/drive/payments/types/types'; -import { CouponCodeData, PartialErrorState } from '../types'; +import { DisplayPrice, UserType, CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { StripeElementsOptions } from '@stripe/stripe-js'; +import { PartialErrorState } from '../types'; describe('checkoutReducer', () => { const createMockPlan = (id: string, currency: string, amount: number, type: UserType): PriceWithTax => ({ @@ -25,7 +25,6 @@ describe('checkoutReducer', () => { expect(initialStateForCheckout.plan).toBeNull(); expect(initialStateForCheckout.isPaying).toBe(false); expect(initialStateForCheckout.authMethod).toBe('signUp'); - expect(initialStateForCheckout.seatsForBusinessSubscription).toBe(1); }); it('changes selected plans without affecting other information', () => { @@ -45,13 +44,11 @@ describe('checkoutReducer', () => { codeId: 'code-id', codeName: 'PROMO123', }, - seatsForBusinessSubscription: 5, }, { type: 'SET_CURRENT_PLAN_SELECTED', payload: mockPlan }, ); expect(setCurrentPlanResult.currentSelectedPlan).toEqual(mockPlan); expect(setCurrentPlanResult.couponCodeData?.codeName).toBe('PROMO123'); - expect(setCurrentPlanResult.seatsForBusinessSubscription).toBe(5); }); it('updates user avatar, country, and prices without losing other data', () => { @@ -83,12 +80,11 @@ describe('checkoutReducer', () => { }, ]; const pricesResult = checkoutReducer( - { ...initialStateForCheckout, country: 'GB', seatsForBusinessSubscription: 3 }, + { ...initialStateForCheckout, country: 'GB' }, { type: 'SET_PRICES', payload: mockPrices }, ); expect(pricesResult.prices).toEqual(mockPrices); expect(pricesResult.country).toBe('GB'); - expect(pricesResult.seatsForBusinessSubscription).toBe(3); }); it('tracks payment and loading status without erasing existing information', () => { @@ -123,12 +119,11 @@ describe('checkoutReducer', () => { expect(readyResult.couponCodeData?.codeName).toBe('WELCOME'); const dialogResult = checkoutReducer( - { ...initialStateForCheckout, isUpdatingSubscription: true, seatsForBusinessSubscription: 10 }, + { ...initialStateForCheckout, isUpdatingSubscription: true }, { type: 'SET_IS_UPDATE_SUBSCRIPTION_DIALOG_OPEN', payload: true }, ); expect(dialogResult.isUpdateSubscriptionDialogOpen).toBe(true); expect(dialogResult.isUpdatingSubscription).toBe(true); - expect(dialogResult.seatsForBusinessSubscription).toBe(10); const updatingResult = checkoutReducer( { ...initialStateForCheckout, isUpdateSubscriptionDialogOpen: true, country: 'DE' }, @@ -141,7 +136,7 @@ describe('checkoutReducer', () => { it('saves promo codes while keeping everything else intact', () => { const promoCodeResult = checkoutReducer( - { ...initialStateForCheckout, isPaying: true, seatsForBusinessSubscription: 7 }, + { ...initialStateForCheckout, isPaying: true }, { type: 'SET_COUPON_CODE_DATA', payload: { @@ -152,7 +147,6 @@ describe('checkoutReducer', () => { ); expect(promoCodeResult.couponCodeData?.codeName).toBe('SUMMER2024'); expect(promoCodeResult.isPaying).toBe(true); - expect(promoCodeResult.seatsForBusinessSubscription).toBe(7); }); it('handles coupons, payment settings, login method, errors, and seats without losing other details', () => { @@ -204,18 +198,6 @@ describe('checkoutReducer', () => { expect(errorResult.error).toEqual(mockError); expect(errorResult.isPaying).toBe(true); expect(errorResult.country).toBe('MX'); - - const seatsResult = checkoutReducer( - { - ...initialStateForCheckout, - couponCodeData: { codeId: 'code-id', codeName: 'BUSINESS' }, - isUpdatingSubscription: true, - }, - { type: 'SET_SEATS_FOR_BUSINESS_SUBSCRIPTION', payload: 5 }, - ); - expect(seatsResult.seatsForBusinessSubscription).toBe(5); - expect(seatsResult.couponCodeData?.codeName).toBe('BUSINESS'); - expect(seatsResult.isUpdatingSubscription).toBe(true); }); it('ignores unknown actions and creates new copies when making changes', () => { diff --git a/src/views/Checkout/store/checkoutReducer.ts b/src/views/Checkout/store/checkoutReducer.ts index 6204c3558..6eafe1f8e 100644 --- a/src/views/Checkout/store/checkoutReducer.ts +++ b/src/views/Checkout/store/checkoutReducer.ts @@ -14,7 +14,6 @@ export const initialStateForCheckout: State = { elementsOptions: undefined, error: undefined, authMethod: 'signUp', - seatsForBusinessSubscription: 1, }; export const checkoutReducer = (state: State, action: Action): State => { @@ -45,8 +44,6 @@ export const checkoutReducer = (state: State, action: Action): State => { return { ...state, authMethod: action.payload }; case 'SET_ERROR': return { ...state, error: action.payload }; - case 'SET_SEATS_FOR_BUSINESS_SUBSCRIPTION': - return { ...state, seatsForBusinessSubscription: action.payload }; default: return state; } diff --git a/src/views/Checkout/store/types.ts b/src/views/Checkout/store/types.ts index 279fe548b..889c86950 100644 --- a/src/views/Checkout/store/types.ts +++ b/src/views/Checkout/store/types.ts @@ -1,5 +1,5 @@ -import { DisplayPrice } from '@internxt/sdk/dist/drive/payments/types/types'; -import { AuthMethodTypes, CouponCodeData, PartialErrorState } from '../types'; +import { CouponCodeData, DisplayPrice } from '@internxt/sdk/dist/drive/payments/types/types'; +import { AuthMethodTypes, PartialErrorState } from '../types'; import { StripeElementsOptions } from '@stripe/stripe-js'; import { PriceWithTax } from '@internxt/sdk/dist/payments/types'; @@ -13,7 +13,6 @@ export interface State { isUpdatingSubscription: boolean; prices: DisplayPrice[]; country: string; - seatsForBusinessSubscription: number; authMethod: AuthMethodTypes; couponCodeData?: CouponCodeData; elementsOptions?: StripeElementsOptions; @@ -30,7 +29,6 @@ export type Action = | { type: 'SET_IS_UPDATING_SUBSCRIPTION'; payload: boolean } | { type: 'SET_PRICES'; payload: DisplayPrice[] } | { type: 'SET_COUNTRY'; payload: string } - | { type: 'SET_SEATS_FOR_BUSINESS_SUBSCRIPTION'; payload: number } | { type: 'SET_COUPON_CODE_DATA'; payload: CouponCodeData | undefined } | { type: 'SET_ELEMENTS_OPTIONS'; payload: StripeElementsOptions } | { type: 'SET_AUTH_METHOD'; payload: AuthMethodTypes } diff --git a/src/views/Checkout/types/checkout.types.ts b/src/views/Checkout/types/checkout.types.ts index 81ec8edad..bd0af7f28 100644 --- a/src/views/Checkout/types/checkout.types.ts +++ b/src/views/Checkout/types/checkout.types.ts @@ -30,7 +30,6 @@ export interface CheckoutViewManager { ) => Promise; onRemoveAppliedCouponCode: () => void; handleAuthMethodChange: (method: AuthMethodTypes) => void; - onSeatsChange: (seat: number) => void; onCurrencyChange: (currency: string) => void; onUserNameChanges: (userName: string) => void; } diff --git a/src/views/Checkout/types/index.ts b/src/views/Checkout/types/index.ts index d572fbb7d..862b16d2e 100644 --- a/src/views/Checkout/types/index.ts +++ b/src/views/Checkout/types/index.ts @@ -1,4 +1,4 @@ -import { DisplayPrice, UserType } from '@internxt/sdk/dist/drive/payments/types/types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { PriceWithTax } from '@internxt/sdk/dist/payments/types'; import { Stripe, StripeElements } from '@stripe/stripe-js'; import { ActionDialog, DialogActionConfig } from 'app/contexts/dialog-manager/ActionDialogManager.context'; @@ -72,22 +72,8 @@ export enum RenewalPeriod { Annually = 'annually', } -export type RequestedPlanData = DisplayPrice & { - decimalAmount: number; - type: UserType; - minimumSeats?: number; - maximumSeats?: number; -}; - export type AuthMethodTypes = 'signUp' | 'signIn' | 'userIsSignedIn'; -export interface CouponCodeData { - codeId: string; - codeName: string; - amountOff?: number; - percentOff?: number; -} - export type ErrorType = 'auth' | 'stripe' | 'coupon'; export type PartialErrorState = Partial>; @@ -99,7 +85,6 @@ export interface CreatePaymentIntentPayload { currency: string; captchaToken: string; userAddress: string; - seatsForBusinessSubscription?: number; promoCodeId?: string; } @@ -116,7 +101,6 @@ export interface ProcessPurchasePayload { openCryptoPaymentDialog?: (key: ActionDialog, config?: DialogActionConfig) => void; translate: (key: string) => string; currentSelectedPlan: PriceWithTax; - seatsForBusinessSubscription?: number; couponCodeData?: CouponCodeData; isFirstPurchase?: boolean; } @@ -136,7 +120,6 @@ export interface UseUserPaymentPayload { captchaToken: string; translate: (key: string) => string; couponCodeData?: CouponCodeData; - seatsForBusinessSubscription?: number; } export enum PlanInterval { diff --git a/src/views/Checkout/utils/getProductAmount.test.ts b/src/views/Checkout/utils/getProductAmount.test.ts index ee6f1726b..05674be44 100644 --- a/src/views/Checkout/utils/getProductAmount.test.ts +++ b/src/views/Checkout/utils/getProductAmount.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { getProductAmount } from './getProductAmount'; -import { CouponCodeData } from '../types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; describe('Calculating final price of a product', () => { it('When there is no coupon, returns base amount multiplied by users', () => { diff --git a/src/views/Checkout/utils/getProductAmount.ts b/src/views/Checkout/utils/getProductAmount.ts index 98a8c414a..9d424ab9b 100644 --- a/src/views/Checkout/utils/getProductAmount.ts +++ b/src/views/Checkout/utils/getProductAmount.ts @@ -1,5 +1,4 @@ -import { DisplayPrice } from '@internxt/sdk/dist/drive/payments/types/types'; -import { CouponCodeData } from '../types'; +import { CouponCodeData, DisplayPrice } from '@internxt/sdk/dist/drive/payments/types/types'; import { formatPrice } from './formatPrice'; export const getProductAmount = ( diff --git a/src/views/Checkout/utils/pcCloud.utils.test.ts b/src/views/Checkout/utils/pcCloud.utils.test.ts deleted file mode 100644 index 4e0a90d02..000000000 --- a/src/views/Checkout/utils/pcCloud.utils.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { envService, localStorageService } from 'services'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { checkoutService } from '../services'; -import { processPcCloudPayment, ProcessPcCloudPaymentProps } from './pcCloud.utils'; -import { PcCloudError } from './utils.errors'; - -const mockedProps: ProcessPcCloudPaymentProps = { - customerId: 'customer_id', - elements: vi.fn() as any, - mobileToken: 'mobile_token', - stripeSDK: { - confirmSetup: vi.fn().mockImplementation(() => Promise.resolve({ error: undefined })), - } as any, - selectedPlan: { - price: { - id: 'price_id', - }, - }, - token: 'token', -}; -const mockHostnameEnv = 'https://api.test.com'; - -describe('PC Cloud utils', () => { - beforeEach(() => { - vi.spyOn(envService, 'getVariable').mockImplementation((env: any) => { - if (env === 'hostname') { - return mockHostnameEnv; - } - return ''; - }); - }); - - describe('Processing a PC cloud payment', () => { - test('When the user wants to purchase a plan from pc cloud, then the setup should be done correctly', async () => { - const mockedClientSecret = 'client_secret'; - const setLocalStorageServiceSpy = vi.spyOn(localStorageService, 'set').mockReturnValue(); - vi.spyOn(checkoutService, 'checkoutSetupIntent').mockResolvedValue({ - clientSecret: mockedClientSecret, - }); - - await processPcCloudPayment(mockedProps); - - expect(setLocalStorageServiceSpy).toHaveBeenCalledWith('customerId', mockedProps.customerId); - expect(setLocalStorageServiceSpy).toHaveBeenCalledWith('token', mockedProps.token); - expect(setLocalStorageServiceSpy).toHaveBeenCalledWith('priceId', mockedProps.selectedPlan.price.id); - expect(setLocalStorageServiceSpy).toHaveBeenCalledWith('customerToken', mockedProps.token); - expect(setLocalStorageServiceSpy).toHaveBeenCalledWith('mobileToken', mockedProps.mobileToken); - expect(mockedProps.stripeSDK.confirmSetup).toHaveBeenCalled(); - expect(mockedProps.stripeSDK.confirmSetup).toHaveBeenCalledWith({ - clientSecret: mockedClientSecret, - elements: mockedProps.elements, - confirmParams: { - return_url: `${mockHostnameEnv}/checkout/pcCloud-success?mobileToken=${mockedProps.mobileToken}&priceId=${mockedProps.selectedPlan.price.id}`, - }, - }); - }); - - test('When the user wants to purchase a plan from pc cloud and an error occurs, then an error indicating so is thrown', async () => { - const mockedClientSecret = 'client_secret'; - const mockedErrorMessage = 'Error while processing the payment'; - const mockedPropsWithError = { - ...mockedProps, - stripeSDK: { - confirmSetup: vi.fn().mockImplementation(() => - Promise.resolve({ - error: { - message: mockedErrorMessage, - }, - }), - ), - } as any, - }; - vi.spyOn(localStorageService, 'set').mockReturnValue(); - vi.spyOn(checkoutService, 'checkoutSetupIntent').mockResolvedValue({ - clientSecret: mockedClientSecret, - }); - - await expect(processPcCloudPayment(mockedPropsWithError)).rejects.toThrow(PcCloudError); - }); - }); -}); diff --git a/src/views/Checkout/utils/pcCloud.utils.ts b/src/views/Checkout/utils/pcCloud.utils.ts deleted file mode 100644 index 2a03cb6ef..000000000 --- a/src/views/Checkout/utils/pcCloud.utils.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { envService, localStorageService } from 'services'; -import { checkoutService } from '../services'; -import { Stripe, StripeElements } from '@stripe/stripe-js'; -import { PcCloudError } from './utils.errors'; - -export interface ProcessPcCloudPaymentProps { - customerId: string; - selectedPlan: any; - token: string; - mobileToken: string; - stripeSDK: Stripe; - elements: StripeElements; -} - -export const processPcCloudPayment = async ({ - customerId, - selectedPlan, - token, - mobileToken, - stripeSDK, - elements, -}: ProcessPcCloudPaymentProps) => { - const setupIntent = await checkoutService.checkoutSetupIntent(customerId); - localStorageService.set('customerId', customerId); - localStorageService.set('token', token); - localStorageService.set('priceId', selectedPlan.price.id); - localStorageService.set('customerToken', token); - localStorageService.set('mobileToken', mobileToken); - const RETURN_URL_DOMAIN = envService.getVariable('hostname'); - const { error: confirmIntentError } = await stripeSDK.confirmSetup({ - elements, - clientSecret: setupIntent.clientSecret, - confirmParams: { - return_url: `${RETURN_URL_DOMAIN}/checkout/pcCloud-success?mobileToken=${mobileToken}&priceId=${selectedPlan.price.id}`, - }, - }); - - if (confirmIntentError) { - throw new PcCloudError(confirmIntentError.message); - } -}; diff --git a/src/views/Checkout/utils/utils.errors.ts b/src/views/Checkout/utils/utils.errors.ts deleted file mode 100644 index 7afa6e045..000000000 --- a/src/views/Checkout/utils/utils.errors.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class PcCloudError extends Error { - constructor(message?: string) { - super(message ?? 'An error occurred while processing your payment'); - this.name = 'PcCloudError'; - } -} diff --git a/src/views/Checkout/views/CheckoutView.tsx b/src/views/Checkout/views/CheckoutView.tsx index 696e83c7b..543d0c137 100644 --- a/src/views/Checkout/views/CheckoutView.tsx +++ b/src/views/Checkout/views/CheckoutView.tsx @@ -1,16 +1,15 @@ -import { UserType } from '@internxt/sdk/dist/drive/payments/types/types'; +import { CouponCodeData } from '@internxt/sdk/dist/drive/payments/types/types'; import { Button, Loader } from '@internxt/ui'; import { AddressElement, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'; import { StripePaymentElementOptions } from '@stripe/stripe-js'; import { IFormValues } from 'app/core/types'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; -import { OptionalB2BDropdown } from '../components/OptionalB2BDropdown'; import { LegacyRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { CheckoutProductCard } from '../components/CheckoutProductCard'; import { CheckoutUserAuth } from '../components/CheckoutUserAuth'; import { HeaderComponent } from '../components/Header'; -import { AuthMethodTypes, CouponCodeData, PaymentType } from '../types'; +import { AuthMethodTypes, PaymentType } from '../types'; import { CheckoutViewManager, UserInfoProps } from '../types/checkout.types'; import { CryptoCurrency, PriceWithTax } from '@internxt/sdk/dist/payments/types'; import { AvailableCryptoCurrenciesDropdown } from '../components/AvailableCryptoCurrenciesDropdown'; @@ -30,7 +29,6 @@ export const PAYMENT_ELEMENT_OPTIONS: StripePaymentElementOptions = { interface CheckoutViewProps { userInfo: UserInfoProps; isUserAuthenticated: boolean; - showHardcodedRenewal?: string; showCouponCode: boolean; userAuthComponentRef: LegacyRef; checkoutViewVariables: { @@ -39,7 +37,6 @@ interface CheckoutViewProps { authError?: string; couponCodeError?: string; couponCodeData?: CouponCodeData; - seatsForBusinessSubscription: number; currentSelectedPlan: PriceWithTax | null; selectedCurrency: string; }; @@ -56,7 +53,6 @@ const CheckoutView = ({ userInfo, isUserAuthenticated, showCouponCode, - showHardcodedRenewal, userAuthComponentRef, checkoutViewVariables, checkoutViewManager, @@ -69,16 +65,8 @@ const CheckoutView = ({ const stripeSDK = useStripe(); const elements = useElements(); const [isCryptoDropdownOpen, setIsCryptoDropdownOpen] = useState(false); - const { - isPaying, - couponCodeError, - authError, - authMethod, - couponCodeData, - seatsForBusinessSubscription, - currentSelectedPlan, - selectedCurrency, - } = checkoutViewVariables; + const { isPaying, couponCodeError, authError, authMethod, couponCodeData, currentSelectedPlan, selectedCurrency } = + checkoutViewVariables; const onCryptoDropdownToggle = () => { if (!isCryptoDropdownOpen) { @@ -127,8 +115,6 @@ const CheckoutView = ({ checkoutViewManager.onCheckoutButtonClicked(formData, event, stripeSDK, elements); }; - const isBusinessPlan = currentSelectedPlan.price.type === UserType.Business; - return (
- {isBusinessPlan ? ( - - ) : undefined}

3. {translate('checkout.paymentTitle')}

@@ -201,11 +184,8 @@ const CheckoutView = ({ diff --git a/src/views/Checkout/views/CheckoutViewWrapper.tsx b/src/views/Checkout/views/CheckoutViewWrapper.tsx index fe30144be..3aa5c3d69 100644 --- a/src/views/Checkout/views/CheckoutViewWrapper.tsx +++ b/src/views/Checkout/views/CheckoutViewWrapper.tsx @@ -45,7 +45,6 @@ import { import { usePromotionalCode } from '../hooks/usePromotionalCode'; import { useAuthCheckout } from '../hooks/useAuthCheckout'; import { checkoutReducer, initialStateForCheckout } from '../store'; -import { processPcCloudPayment } from '../utils/pcCloud.utils'; import { CheckoutLoader } from '../components/CheckoutLoader'; const CheckoutViewWrapper = () => { @@ -54,13 +53,8 @@ const CheckoutViewWrapper = () => { const user = useSelector((state) => state.user.user); const [state, dispatchReducer] = useReducer(checkoutReducer, initialStateForCheckout); const { authMethod, isPaying, isUpdateSubscriptionDialogOpen, isUpdatingSubscription } = state; - const { - setAuthMethod, - setIsUserPaying, - setSeatsForBusinessSubscription, - setIsUpdateSubscriptionDialogOpen, - setIsUpdatingSubscription, - } = useCheckout(dispatchReducer); + const { setAuthMethod, setIsUserPaying, setIsUpdateSubscriptionDialogOpen, setIsUpdatingSubscription } = + useCheckout(dispatchReducer); const { planId, promotionCode, currency, paramMobileToken, gclid } = useCheckoutQueryParams(); const { location: userLocationData } = useUserLocation(); @@ -70,7 +64,7 @@ const CheckoutViewWrapper = () => { promoCodeName: promotionCode, }); - const { selectedPlan, businessSeats, fetchSelectedPlan } = useProducts({ + const { selectedPlan, fetchSelectedPlan } = useProducts({ currency: currency ?? 'eur', translate, planId, @@ -108,8 +102,6 @@ const CheckoutViewWrapper = () => { const gclidStored = localStorageService.get(STORAGE_KEYS.GCLID); - const renewsAtPCComp = `${translate('checkout.productCard.pcMobileRenews')}`; - const canChangePlanDialogBeOpened = selectedPlan?.price && isUpdateSubscriptionDialogOpen; const isCryptoPaymentDialogOpen = isDialogOpen(CRYPTO_PAYMENT_DIALOG_KEY); @@ -164,7 +156,7 @@ const CheckoutViewWrapper = () => { storage: selectedPlan.price.bytes.toString(), promoCodeId: promotionCode ?? undefined, couponCodeData: promoCodeData, - seats: selectedPlan.price.type === 'business' ? businessSeats : 1, + seats: 1, }); metaService.trackCheckoutStart({ @@ -345,34 +337,22 @@ const CheckoutViewWrapper = () => { metadata, }); - if (paramMobileToken) { - await processPcCloudPayment({ - customerId, - selectedPlan, - token, - mobileToken: paramMobileToken, - stripeSDK, - elements, - }); - } else { - await handleUserPayment({ - confirmPayment: stripeSDK.confirmPayment, - confirmSetupIntent: stripeSDK.confirmSetup, - couponCodeData: promoCodeData, - currency: selectedCurrency ?? selectedPlan.price.currency, - priceId: selectedPlan.price.id, - customerId, - elements, - translate, - selectedPlan, - token, - gclidStored, - captchaToken, - seatsForBusinessSubscription: businessSeats, - openCryptoPaymentDialog, - userAddress: userLocationData?.ip as string, - }); - } + await handleUserPayment({ + confirmPayment: stripeSDK.confirmPayment, + confirmSetupIntent: stripeSDK.confirmSetup, + couponCodeData: promoCodeData, + currency: selectedCurrency ?? selectedPlan.price.currency, + priceId: selectedPlan.price.id, + customerId, + elements, + translate, + selectedPlan, + token, + gclidStored, + captchaToken, + openCryptoPaymentDialog, + userAddress: userLocationData?.ip as string, + }); } catch (err) { const statusCode = (err as any).status; const castedError = errorService.castError(err); @@ -400,24 +380,6 @@ const CheckoutViewWrapper = () => { setUserName(userName); }; - const onSeatsChange = (seats: number) => { - if (!selectedPlan?.price) { - console.warn('Cannot change seats: no selected plan available'); - return; - } - - const minSeats = selectedPlan.price.minimumSeats; - const maxSeats = selectedPlan.price.maximumSeats; - - if (maxSeats && seats > maxSeats) { - setSeatsForBusinessSubscription(maxSeats); - } else if (minSeats && seats < minSeats) { - setSeatsForBusinessSubscription(minSeats); - } else { - setSeatsForBusinessSubscription(seats); - } - }; - const onCurrencyTypeChanges = (currency: PaymentType) => { setCurrencyType(currency); }; @@ -429,7 +391,6 @@ const CheckoutViewWrapper = () => { onRemoveAppliedCouponCode: removeCouponCode, onUserAddressChanges, handleAuthMethodChange: setAuthMethod, - onSeatsChange, onCurrencyChange: setSelectedCurrency, onUserNameChanges, }; @@ -445,7 +406,6 @@ const CheckoutViewWrapper = () => { couponCodeData: promoCodeData, couponCodeError: couponError ?? undefined, authError: authError ?? undefined, - seatsForBusinessSubscription: businessSeats, currentSelectedPlan: selectedPlan, selectedCurrency: currency ?? selectedPlan.price.currency, }} @@ -453,7 +413,6 @@ const CheckoutViewWrapper = () => { showCouponCode={!paramMobileToken} userInfo={userInfo} isUserAuthenticated={isAuthenticated} - showHardcodedRenewal={paramMobileToken ? renewsAtPCComp : undefined} checkoutViewManager={checkoutViewManager} availableCryptoCurrencies={availableCryptoCurrencies} onCurrencyTypeChanges={onCurrencyTypeChanges} diff --git a/src/views/Checkout/views/PcCloudSuccess.tsx b/src/views/Checkout/views/PcCloudSuccess.tsx deleted file mode 100644 index fc7c24817..000000000 --- a/src/views/Checkout/views/PcCloudSuccess.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { trackPaymentConversion } from 'app/analytics/impact.service'; -import localStorageService from 'services/local-storage.service'; -import paymentService from '../services/payment.service'; -import { useAppDispatch } from 'app/store/hooks'; -import { planThunks } from 'app/store/slices/plan'; -import { userThunks } from 'app/store/slices/user'; -import { useCallback, useEffect } from 'react'; -import { removePaymentsStorage } from './CheckoutSuccessView'; - -const PcCloudSuccess = () => { - const dispatch = useAppDispatch(); - - const onPcCloudSuccess = useCallback( - async ({ - customerId, - priceId, - token, - mobileTokenParam, - currency, - }: { - customerId: string; - priceId: string; - token: string; - mobileTokenParam: string; - currency: string; - }) => { - if (customerId && priceId && token && mobileTokenParam) { - await paymentService.createSubscriptionWithTrial(customerId, priceId, token, mobileTokenParam, currency); - } - setTimeout(async () => { - await dispatch(userThunks.initializeUserThunk()); - await dispatch(planThunks.initializeThunk()); - }, 3000); - - try { - await trackPaymentConversion(); - removePaymentsStorage(); - } catch (err) { - console.log('Analytics error: ', err); - } - const deepLinkUrl = 'com.internxt.pccloud://checkout'; - globalThis.location.href = deepLinkUrl; - }, - [dispatch], - ); - - useEffect(() => { - const customerId = localStorageService.get('customerId'); - const token = localStorageService.get('customerToken'); - const currency = localStorageService.get('currency') ?? 'eur'; - const priceId = localStorageService.get('priceId'); - const mobileTokenParam = localStorageService.get('mobileToken'); - - if (customerId && priceId && token && mobileTokenParam) { - onPcCloudSuccess({ - customerId, - priceId, - token, - mobileTokenParam, - currency, - }); - } - }, []); - return <>; -}; - -export default PcCloudSuccess; diff --git a/src/views/Checkout/views/index.ts b/src/views/Checkout/views/index.ts index ec012e725..1d2919f92 100644 --- a/src/views/Checkout/views/index.ts +++ b/src/views/Checkout/views/index.ts @@ -1,5 +1,4 @@ export { default as CheckoutCancelView } from './CheckoutCancelView'; export { CheckoutSessionId } from './CheckoutSessionId'; export { default as CheckoutSuccessView } from './CheckoutSuccessView'; -export { default as PcCloudSuccess } from './PcCloudSuccess'; export { default as CheckoutViewWrapper } from './CheckoutViewWrapper'; diff --git a/src/views/NewSettings/utils/suscriptionUtils.test.ts b/src/views/NewSettings/utils/suscriptionUtils.test.ts index 9b02db7f6..781f9b48f 100644 --- a/src/views/NewSettings/utils/suscriptionUtils.test.ts +++ b/src/views/NewSettings/utils/suscriptionUtils.test.ts @@ -24,7 +24,6 @@ vi.mock('views/Checkout/services', () => ({ paymentService: {}, authCheckoutService: {}, checkoutService: {}, - fetchProducts: vi.fn(), ProductService: {}, })); diff --git a/src/views/Signup/ShareGuestSignUpView.test.tsx b/src/views/Signup/ShareGuestSignUpView.test.tsx index 81b105861..8ddbfbac4 100644 --- a/src/views/Signup/ShareGuestSignUpView.test.tsx +++ b/src/views/Signup/ShareGuestSignUpView.test.tsx @@ -239,11 +239,6 @@ describe('onSubmit', () => { initializeThunk: vi.fn(), }, })); - vi.mock('app/store/slices/products', () => ({ - productsThunks: { - initializeThunk: vi.fn(), - }, - })); vi.mock('app/store/slices/referrals', () => ({ referralsThunks: { diff --git a/src/views/Signup/WorkspaceGuestSignUpView.test.tsx b/src/views/Signup/WorkspaceGuestSignUpView.test.tsx index 4a0e17831..710adcb46 100644 --- a/src/views/Signup/WorkspaceGuestSignUpView.test.tsx +++ b/src/views/Signup/WorkspaceGuestSignUpView.test.tsx @@ -194,12 +194,6 @@ describe('onSubmit', () => { }, })); - vi.mock('../../app/store/slices/products', () => ({ - productsThunks: { - initializeThunk: vi.fn(), - }, - })); - vi.mock('../../app/store/slices/referrals', () => ({ referralsThunks: { initializeThunk: vi.fn(), diff --git a/src/views/Signup/utils/guestSignupOnSubmit.test.ts b/src/views/Signup/utils/guestSignupOnSubmit.test.ts index 0c1abb431..2daad372e 100644 --- a/src/views/Signup/utils/guestSignupOnSubmit.test.ts +++ b/src/views/Signup/utils/guestSignupOnSubmit.test.ts @@ -7,7 +7,6 @@ import localStorageService from 'services/local-storage.service'; import navigationService from 'services/navigation.service'; import { parseAndDecryptUserKeys } from 'app/crypto/services/keys.service'; import { userActions, userThunks } from 'app/store/slices/user'; -import { productsThunks } from 'app/store/slices/products'; import { planThunks } from 'app/store/slices/plan'; vi.mock(import('services/error.service')); @@ -15,7 +14,6 @@ vi.mock(import('services/local-storage.service')); vi.mock(import('services/navigation.service')); vi.mock(import('app/crypto/services/keys.service')); vi.mock(import('app/store/slices/user')); -vi.mock(import('app/store/slices/products')); vi.mock(import('app/store/slices/plan')); vi.mock(import('app/store/slices/referrals')); @@ -68,7 +66,6 @@ describe('guestSignupOnSubmit', () => { mockDispatch.mockResolvedValue(undefined); vi.mocked(parseAndDecryptUserKeys).mockReturnValue(mockParsedKeys); vi.mocked(userThunks.initializeUserThunk).mockReturnValue({} as any); - vi.mocked(productsThunks.initializeThunk).mockReturnValue({} as any); vi.mocked(planThunks.initializeThunk).mockReturnValue({} as any); }); @@ -102,7 +99,6 @@ describe('guestSignupOnSubmit', () => { expect(parseAndDecryptUserKeys).toHaveBeenCalledWith(mockRegistrationResponse.xUser, 'password123'); expect(mockDispatch).toHaveBeenCalledWith(userActions.setUser(expect.objectContaining({ uuid: 'user-uuid' }))); expect(mockDispatch).toHaveBeenCalledWith(userThunks.initializeUserThunk()); - expect(mockDispatch).toHaveBeenCalledWith(productsThunks.initializeThunk()); expect(mockDispatch).toHaveBeenCalledWith(planThunks.initializeThunk()); expect(navigationService.push).toHaveBeenCalledWith(AppView.Drive); expect(mockSetShowError).toHaveBeenCalledWith(true); diff --git a/src/views/Signup/utils/guestSignupOnSubmit.ts b/src/views/Signup/utils/guestSignupOnSubmit.ts index efa0f1541..b7043408b 100644 --- a/src/views/Signup/utils/guestSignupOnSubmit.ts +++ b/src/views/Signup/utils/guestSignupOnSubmit.ts @@ -5,7 +5,6 @@ import localStorageService from 'services/local-storage.service'; import navigationService from 'services/navigation.service'; import { parseAndDecryptUserKeys } from 'app/crypto/services/keys.service'; import { userActions, userThunks } from 'app/store/slices/user'; -import { productsThunks } from 'app/store/slices/products'; import { planThunks } from 'app/store/slices/plan'; import { AppDispatch } from 'app/store'; @@ -74,7 +73,6 @@ export const guestSignupOnSubmit = async ({ dispatch(userActions.setUser(user)); await dispatch(userThunks.initializeUserThunk()); - dispatch(productsThunks.initializeThunk()); dispatch(planThunks.initializeThunk()); return navigationService.push(redirectTo); diff --git a/yarn.lock b/yarn.lock index 76461968e..ad9b66f7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1906,13 +1906,12 @@ version "1.0.2" resolved "https://codeload.github.com/internxt/prettier-config/tar.gz/9fa74e9a2805e1538b50c3809324f1c9d0f3e4f9" -"@internxt/sdk@=1.15.13": - version "1.15.13" - resolved "https://registry.yarnpkg.com/@internxt/sdk/-/sdk-1.15.13.tgz#4a754355ea619255d8db9ca23efc3024e840ab2c" - integrity sha512-GQeCWLScG63tCbfyoXkOC4DP79PsSFrh7k4BUG8ejn6+6B8RO9P1ilhO91cPIP3cdtA6oo749ysH9TQH+0GcMw== +"@internxt/sdk@=1.16.1": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@internxt/sdk/-/sdk-1.16.1.tgz#6f93847ec72237b491f33dbcf128766d76cdc25a" + integrity sha512-twO5fvqBk5vuhX4SR+SUwq7nPg+Cq6JkxIpAaATBWGWk0TUaCKSgDx9gw8rl3IGy+DmHw6p85eDC6hQjRbjiKw== dependencies: - axios "1.15.0" - internxt-crypto "1.0.2" + axios "^1.16.0" "@internxt/ui@=0.1.15": version "0.1.15" @@ -2040,48 +2039,11 @@ outvariant "^1.4.3" strict-event-emitter "^0.5.1" -"@noble/ciphers@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-2.1.1.tgz#c8c74fcda8c3d1f88797d0ecda24f9fc8b92b052" - integrity sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw== - -"@noble/curves@^2.0.1": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-2.2.0.tgz#981be3aadc3bbfbcdb245e78cc97aa6f759246c2" - integrity sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ== - dependencies: - "@noble/hashes" "2.2.0" - -"@noble/curves@~2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-2.0.1.tgz#64ba8bd5e8564a02942655602515646df1cdb3ad" - integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw== - dependencies: - "@noble/hashes" "2.0.1" - -"@noble/hashes@2.0.1", "@noble/hashes@^2.0.1", "@noble/hashes@~2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.0.1.tgz#fc1a928061d1232b0a52bb754393c37a5216c89e" - integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== - -"@noble/hashes@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.2.0.tgz#22da1d16a469954fce877055d559900a6c73b63b" - integrity sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg== - "@noble/hashes@^1.2.0": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/post-quantum@^0.5.2": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@noble/post-quantum/-/post-quantum-0.5.4.tgz#bd1095647c61e4c8fd317fa8a3977db8cd28a4b9" - integrity sha512-leww0zzIirrvwaYMPI9fj6aRIlA/c6Y0/lifQQ1YOOyHEr0MNH3yYpjXeiVG+tWdPps4XxGclFWX2INPO3Yo5w== - dependencies: - "@noble/curves" "~2.0.0" - "@noble/hashes" "~2.0.0" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3058,19 +3020,6 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c" integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== -"@scure/base@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.0.0.tgz#ba6371fddf92c2727e88ad6ab485db6e624f9a98" - integrity sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== - -"@scure/bip39@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-2.0.1.tgz#47a6dc15e04faf200041239d46ae3bb7c3c96add" - integrity sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg== - dependencies: - "@noble/hashes" "2.0.1" - "@scure/base" "2.0.0" - "@socket.io/component-emitter@~3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" @@ -4026,7 +3975,7 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@1.15.0, axios@^1.15.0: +axios@^1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.0.tgz#0fcee91ef03d386514474904b27863b2c683bf4f" integrity sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q== @@ -4035,6 +3984,15 @@ axios@1.15.0, axios@^1.15.0: form-data "^4.0.5" proxy-from-env "^2.1.0" +axios@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.0.tgz#f8e5dd931cef2a5f8c32216d5784eda2f8750eb7" + integrity sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w== + dependencies: + follow-redirects "^1.16.0" + form-data "^4.0.5" + proxy-from-env "^2.1.0" + babel-plugin-emotion@^10.0.27: version "10.2.2" resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d" @@ -5817,12 +5775,7 @@ flatted@^3.2.9, flatted@^3.3.1: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== -flexsearch@^0.8.205: - version "0.8.212" - resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.212.tgz#b9509af778a991b938292e36fe0809a4ece4b940" - integrity sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw== - -follow-redirects@^1.15.11: +follow-redirects@^1.15.11, follow-redirects@^1.16.0: version "1.16.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== @@ -6176,7 +6129,7 @@ hash-base@~3.0, hash-base@~3.0.4: inherits "^2.0.4" safe-buffer "^5.2.1" -hash-wasm@^4.11.0, hash-wasm@^4.12.0: +hash-wasm@^4.11.0: version "4.12.0" resolved "https://registry.yarnpkg.com/hash-wasm/-/hash-wasm-4.12.0.tgz#f9f1a9f9121e027a9acbf6db5d59452ace1ef9bb" integrity sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ== @@ -6266,11 +6219,6 @@ husky@^7.0.2: resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== -husky@^9.1.7: - version "9.1.7" - resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" - integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== - i18next-browser-languagedetector@^7.2.0: version "7.2.1" resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f" @@ -6290,11 +6238,6 @@ idb@^6.1.5: resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.5.tgz#dbc53e7adf1ac7c59f9b2bf56e00b4ea4fce8c7b" integrity sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw== -idb@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.3.tgz#c91e558f15a8d53f1d7f53a094d226fc3ad71fd9" - integrity sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg== - ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -6366,22 +6309,6 @@ ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -internxt-crypto@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/internxt-crypto/-/internxt-crypto-1.0.2.tgz#983fe991dfbb00a453e93070bb34049a88a94707" - integrity sha512-F9PuXci0eU1wlgDwqEbGR7hVDNS0MX8VNh/W+pdpR4ZsEsjRDBrOD2g1DvViR2woCxPiu1AW9Wwekpw2YVKfnA== - dependencies: - "@noble/ciphers" "^2.1.1" - "@noble/curves" "^2.0.1" - "@noble/hashes" "^2.0.1" - "@noble/post-quantum" "^0.5.2" - "@scure/bip39" "^2.0.1" - flexsearch "^0.8.205" - hash-wasm "^4.12.0" - husky "^9.1.7" - idb "^8.0.3" - uuid "^13.0.0" - invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -9588,11 +9515,6 @@ uuid@^11.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== -uuid@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" - integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== - uuid@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-14.0.0.tgz#0af883220163d264ffe0c084f6b8a89b9666966d" From 26fd9671822cc1544472495ba5911c7dbe3730ac Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Thu, 7 May 2026 15:21:56 +0200 Subject: [PATCH 2/3] refactor: remove useless type --- src/services/error.service.test.ts | 4 ++-- src/views/Drive/components/DriveExplorer/types.ts | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/services/error.service.test.ts b/src/services/error.service.test.ts index 17a3294dd..11019f565 100644 --- a/src/services/error.service.test.ts +++ b/src/services/error.service.test.ts @@ -35,13 +35,13 @@ describe('Error Service', () => { status, headers: xRequestId ? { 'x-request-id': xRequestId } : {}, } as AxiosResponse; - return new AxiosResponseError('Request failed with status code ' + status, 'GET /api/test', response); + return new AxiosResponseError('Request failed with status code ' + status, 'GET /api/test', response as any); }; const createAxiosUnknownError = (message: string, hasRequest: boolean, code?: string): AxiosUnknownError => { const axiosErr = new AxiosError(message, code); if (hasRequest) (axiosErr as any).request = {}; - return new AxiosUnknownError(message, 'GET /api/test', axiosErr); + return new AxiosUnknownError(message, 'GET /api/test', axiosErr as any); }; describe('reportError', () => { diff --git a/src/views/Drive/components/DriveExplorer/types.ts b/src/views/Drive/components/DriveExplorer/types.ts index a00b43f40..8ad704418 100644 --- a/src/views/Drive/components/DriveExplorer/types.ts +++ b/src/views/Drive/components/DriveExplorer/types.ts @@ -1,18 +1,6 @@ import { Dispatch, SetStateAction } from 'react'; import { DriveItemData } from 'app/drive/types'; -export enum DriveItemAction { - Rename, - Download, - Share, - Info, - Delete, - ShareCopyLink, - ShareGetLink, - ShareSettings, - ShareDeleteLink, -} - export interface DriveExplorerItemProps { item: DriveItemData; isTrash?: boolean; From 17b07a7dad17806224a813ffa8192d9ee3ce6133 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Thu, 7 May 2026 16:24:46 +0200 Subject: [PATCH 3/3] test: add coverage --- .../hooks/useInitializeCheckout.test.ts | 56 +++++++++++++++++++ .../services/checkout.service.test.ts | 29 ++++++---- .../Checkout/services/checkout.service.ts | 22 +------- 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/src/views/Checkout/hooks/useInitializeCheckout.test.ts b/src/views/Checkout/hooks/useInitializeCheckout.test.ts index 307c42656..b9c30b473 100644 --- a/src/views/Checkout/hooks/useInitializeCheckout.test.ts +++ b/src/views/Checkout/hooks/useInitializeCheckout.test.ts @@ -225,6 +225,62 @@ describe('Initialize checkout custom hook', () => { }); }); + describe('Business plan restriction', () => { + const mockBusinessPriceWithTax: PriceWithTax = { + ...mockPriceWithTax, + price: { + ...mockPriceWithTax.price, + type: UserType.Business, + }, + }; + + test('When the selected plan is a business plan, then a warning notification is shown', async () => { + const props = { + checkoutTheme: 'light', + price: mockBusinessPriceWithTax, + translate: mockTranslate, + }; + + renderHook(() => useInitializeCheckout(props)); + + await waitFor(() => { + expect(notificationsService.show).toHaveBeenCalledWith({ + text: 'checkout.error.businessPlan', + type: ToastType.Warning, + }); + }); + }); + + test('When the selected plan is a business plan and the user is not logged in, then the user is redirected to the signup page', async () => { + const props = { + checkoutTheme: 'light', + price: mockBusinessPriceWithTax, + translate: mockTranslate, + }; + + renderHook(() => useInitializeCheckout(props)); + + await waitFor(() => { + expect(navigationService.push).toHaveBeenCalledWith(AppView.Signup); + }); + }); + + test('When the selected plan is a business plan and the user is logged in, then the user is redirected to the drive page', async () => { + const props = { + checkoutTheme: 'light', + price: mockBusinessPriceWithTax, + user: mockUser, + translate: mockTranslate, + }; + + renderHook(() => useInitializeCheckout(props)); + + await waitFor(() => { + expect(navigationService.push).toHaveBeenCalledWith(AppView.Drive); + }); + }); + }); + describe('Checkout ready state', () => { test('When all initialization is complete, then the checkout is ready', async () => { const props = { diff --git a/src/views/Checkout/services/checkout.service.test.ts b/src/views/Checkout/services/checkout.service.test.ts index 30e02b1e9..a4c70fce8 100644 --- a/src/views/Checkout/services/checkout.service.test.ts +++ b/src/views/Checkout/services/checkout.service.test.ts @@ -36,6 +36,14 @@ vi.mock('app/core/factory/sdk', () => ({ createUsersClient: vi.fn().mockResolvedValue({ handleIncompleteCheckout: vi.fn(), }), + createPaymentsClient: vi.fn().mockResolvedValue({ + fetchPromotionCodeByName: vi.fn().mockResolvedValue({ + codeId: 'promo_123', + codeName: 'PROMO10', + amountOff: undefined, + percentOff: 10, + }), + }), createCheckoutClient: vi.fn().mockResolvedValue({ createCustomer: vi.fn().mockResolvedValue({ customerId: 'cus_123', @@ -70,7 +78,6 @@ vi.mock('app/core/factory/sdk', () => ({ id: 'py_id', invoiceStatus: 'paid', }), - fetchPrices: vi.fn().mockResolvedValue([{ id: 'price_1', currency: 'eur', amount: 1000 }]), verifyCryptoPayment: vi.fn().mockResolvedValue(true), }), }), @@ -242,19 +249,17 @@ describe('Checkout Service tests', () => { }); describe('Fetch promotion code by name', () => { - it('When a valid promo code is passed, then it returns correct promo data', async () => { - vi.spyOn(checkoutService, 'fetchPromotionCodeByName').mockResolvedValue({ - codeId: 'promo_123', - codeName: 'PROMO', - amountOff: 500, - percentOff: undefined, - }); - const result = await checkoutService.fetchPromotionCodeByName('price_123', 'PROMO'); + test('When a valid promotion code name is provided, then the promotion code details are returned', async () => { + const priceId = 'price_123'; + const promotionCodeName = 'PROMO10'; + + const result = await checkoutService.fetchPromotionCodeByName(priceId, promotionCodeName); + expect(result).toEqual({ codeId: 'promo_123', - codeName: 'PROMO', - amountOff: 500, - percentOff: undefined, + codeName: 'PROMO10', + amountOff: undefined, + percentOff: 10, }); }); }); diff --git a/src/views/Checkout/services/checkout.service.ts b/src/views/Checkout/services/checkout.service.ts index 78b2c83d5..085b1bbc7 100644 --- a/src/views/Checkout/services/checkout.service.ts +++ b/src/views/Checkout/services/checkout.service.ts @@ -1,9 +1,4 @@ -import { - CouponCodeData, - CreatedSubscriptionData, - DisplayPrice, - UserType, -} from '@internxt/sdk/dist/drive/payments/types/types'; +import { CouponCodeData, CreatedSubscriptionData } from '@internxt/sdk/dist/drive/payments/types/types'; import axios from 'axios'; import localStorageService from 'services/local-storage.service'; import { SdkFactory } from 'app/core/factory/sdk'; @@ -117,20 +112,6 @@ export const createPaymentIntent = async ({ }); }; -const fetchPrices = async (userType: UserType, currency: string): Promise => { - const PAYMENTS_API_URL = envService.getVariable('payments'); - const response = await fetch(`${PAYMENTS_API_URL}/prices?userType=${userType}¤cy=${currency}`); - - if (response.status !== 200) { - const message = await response.text(); - throw new Error(message); - } - - const dataJson = await response.json(); - - return dataJson; -}; - const checkoutSetupIntent = async (customerId: string) => { try { const newToken = localStorageService.get(LocalStorageItem.NewToken); @@ -258,7 +239,6 @@ const checkoutService = { getPriceById, createSubscription, loadStripeElements, - fetchPrices, checkoutSetupIntent, verifyCryptoPayment, trackIncompleteCheckout,