From ab36c80a2631f7e5903400c19dc8b7f1af6ccb26 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:38:33 -0500 Subject: [PATCH] Merge pull request #21954 from overleaf/em-revert-ai-add-on-deploy Revert faulty AI add-on deploy GitOrigin-RevId: 923ec89f01d4951596f83fcf58b820c39db1e54a --- package-lock.json | 27 ---- .../Features/Subscription/RecurlyClient.js | 140 ++++-------------- .../Features/Subscription/RecurlyEntities.js | 86 +---------- .../Features/Subscription/RecurlyWrapper.js | 7 - .../Subscription/SubscriptionController.js | 109 ++++---------- .../Subscription/SubscriptionHandler.js | 22 --- .../Subscription/SubscriptionRouter.mjs | 6 - .../web/frontend/extracted-translations.json | 2 - .../dashboard/personal-subscription.tsx | 12 +- .../states/active/active-ai-addon.tsx | 8 +- .../modals/cancel-ai-add-on-modal.tsx | 4 +- .../preview-subscription-change/root.tsx | 14 +- .../subscription/data/add-on-codes.ts | 6 +- services/web/locales/en.json | 2 - services/web/package.json | 5 +- .../src/Subscription/RecurlyClientTests.js | 5 +- .../src/Subscription/RecurlyEntitiesTest.js | 64 -------- .../subscription-change-preview.ts | 15 +- 18 files changed, 89 insertions(+), 445 deletions(-) diff --git a/package-lock.json b/package-lock.json index a17927f768..e16ad3b12d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41611,7 +41611,6 @@ "mocha": "^10.2.0", "mocha-each": "^2.0.1", "mock-fs": "^5.1.2", - "nock": "^13.5.6", "nvd3": "^1.8.6", "overleaf-editor-core": "*", "pdfjs-dist": "4.6.82", @@ -43737,20 +43736,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "services/web/node_modules/nock": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", - "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 10.13" - } - }, "services/web/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -51189,7 +51174,6 @@ "mongoose": "8.5.3", "multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e", "nocache": "^2.1.0", - "nock": "^13.5.6", "node-fetch": "^2.7.0", "nodemailer": "^6.7.0", "nodemailer-ses-transport": "^1.5.1", @@ -52760,17 +52744,6 @@ } } }, - "nock": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", - "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - } - }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", diff --git a/services/web/app/src/Features/Subscription/RecurlyClient.js b/services/web/app/src/Features/Subscription/RecurlyClient.js index d442ad45be..44a5c5600e 100644 --- a/services/web/app/src/Features/Subscription/RecurlyClient.js +++ b/services/web/app/src/Features/Subscription/RecurlyClient.js @@ -13,7 +13,6 @@ const { PaypalPaymentMethod, CreditCardPaymentMethod, RecurlyAddOn, - RecurlyPlan, } = require('./RecurlyEntities') /** @@ -68,32 +67,6 @@ async function getSubscription(subscriptionId) { return subscriptionFromApi(subscription) } -/** - * Get the subscription for a given user - * - * @param {string} userId - * @return {Promise} - */ -async function getSubscriptionForUser(userId) { - const subscriptions = await client.listAccountSubscriptions( - `code-${userId}`, - { params: { state: 'active', limit: 2 } } - ) - let result = null - for await (const subscription of subscriptions.each()) { - if (result != null) { - throw new OError('User has more than one Recurly subscription', { - userId, - }) - } - result = subscription - } - if (result == null) { - return null - } - return subscriptionFromApi(result) -} - /** * Request a susbcription change from Recurly * @@ -188,17 +161,6 @@ async function getAddOn(planCode, addOnCode) { return addOnFromApi(addOn) } -/** - * Get the configuration for a given plan - * - * @param {string} planCode - * @return {Promise} - */ -async function getPlan(planCode) { - const plan = await client.getPlan(`code-${planCode}`) - return planFromApi(plan) -} - function subscriptionIsCanceledOrExpired(subscription) { const state = subscription?.recurlyStatus?.state return state === 'canceled' || state === 'expired' @@ -207,53 +169,41 @@ function subscriptionIsCanceledOrExpired(subscription) { /** * Build a RecurlySubscription from Recurly API data * - * @param {recurly.Subscription} apiSubscription + * @param {recurly.Subscription} subscription * @return {RecurlySubscription} */ -function subscriptionFromApi(apiSubscription) { +function subscriptionFromApi(subscription) { if ( - apiSubscription.uuid == null || - apiSubscription.plan == null || - apiSubscription.plan.code == null || - apiSubscription.plan.name == null || - apiSubscription.account == null || - apiSubscription.account.code == null || - apiSubscription.unitAmount == null || - apiSubscription.subtotal == null || - apiSubscription.total == null || - apiSubscription.currency == null || - apiSubscription.currentPeriodStartedAt == null || - apiSubscription.currentPeriodEndsAt == null + subscription.uuid == null || + subscription.plan == null || + subscription.plan.code == null || + subscription.plan.name == null || + subscription.account == null || + subscription.account.code == null || + subscription.unitAmount == null || + subscription.subtotal == null || + subscription.total == null || + subscription.currency == null || + subscription.currentPeriodStartedAt == null || + subscription.currentPeriodEndsAt == null ) { - throw new OError('Invalid Recurly subscription', { - subscription: apiSubscription, - }) + throw new OError('Invalid Recurly subscription', { subscription }) } - - const subscription = new RecurlySubscription({ - id: apiSubscription.uuid, - userId: apiSubscription.account.code, - planCode: apiSubscription.plan.code, - planName: apiSubscription.plan.name, - planPrice: apiSubscription.unitAmount, - addOns: (apiSubscription.addOns ?? []).map(subscriptionAddOnFromApi), - subtotal: apiSubscription.subtotal, - taxRate: apiSubscription.taxInfo?.rate ?? 0, - taxAmount: apiSubscription.tax ?? 0, - total: apiSubscription.total, - currency: apiSubscription.currency, - periodStart: apiSubscription.currentPeriodStartedAt, - periodEnd: apiSubscription.currentPeriodEndsAt, + return new RecurlySubscription({ + id: subscription.uuid, + userId: subscription.account.code, + planCode: subscription.plan.code, + planName: subscription.plan.name, + planPrice: subscription.unitAmount, + addOns: (subscription.addOns ?? []).map(subscriptionAddOnFromApi), + subtotal: subscription.subtotal, + taxRate: subscription.taxInfo?.rate ?? 0, + taxAmount: subscription.tax ?? 0, + total: subscription.total, + currency: subscription.currency, + periodStart: subscription.currentPeriodStartedAt, + periodEnd: subscription.currentPeriodEndsAt, }) - - if (apiSubscription.pendingChange != null) { - subscription.pendingChange = subscriptionChangeFromApi( - subscription, - apiSubscription.pendingChange - ) - } - - return subscription } /** @@ -301,22 +251,14 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) { const nextAddOns = (subscriptionChange.addOns ?? []).map( subscriptionAddOnFromApi ) - - let immediateCharge = - subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0 - for (const creditInvoice of subscriptionChange.invoiceCollection - ?.creditInvoices ?? []) { - // The credit invoice totals are already negative - immediateCharge += creditInvoice.total ?? 0 - } - return new RecurlySubscriptionChange({ subscription, nextPlanCode: subscriptionChange.plan.code, nextPlanName: subscriptionChange.plan.name, nextPlanPrice: subscriptionChange.unitAmount, nextAddOns, - immediateCharge, + immediateCharge: + subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0, }) } @@ -361,22 +303,6 @@ function addOnFromApi(addOn) { }) } -/** - * Build a RecurlyPlan from Recurly API data - * - * @param {recurly.Plan} plan - * @return {RecurlyPlan} - */ -function planFromApi(plan) { - if (plan.code == null || plan.name == null) { - throw new OError('Invalid Recurly add-on', { plan }) - } - return new RecurlyPlan({ - code: plan.code, - name: plan.name, - }) -} - /** * Build an API request from a RecurlySubscriptionChangeRequest * @@ -413,7 +339,6 @@ module.exports = { getAccountForUserId: callbackify(getAccountForUserId), createAccountForUserId: callbackify(createAccountForUserId), getSubscription: callbackify(getSubscription), - getSubscriptionForUser: callbackify(getSubscriptionForUser), previewSubscriptionChange: callbackify(previewSubscriptionChange), applySubscriptionChangeRequest: callbackify(applySubscriptionChangeRequest), removeSubscriptionChange: callbackify(removeSubscriptionChange), @@ -422,12 +347,10 @@ module.exports = { cancelSubscriptionByUuid: callbackify(cancelSubscriptionByUuid), getPaymentMethod: callbackify(getPaymentMethod), getAddOn: callbackify(getAddOn), - getPlan: callbackify(getPlan), subscriptionIsCanceledOrExpired, promises: { getSubscription, - getSubscriptionForUser, getAccountForUserId, createAccountForUserId, previewSubscriptionChange, @@ -438,6 +361,5 @@ module.exports = { cancelSubscriptionByUuid, getPaymentMethod, getAddOn, - getPlan, }, } diff --git a/services/web/app/src/Features/Subscription/RecurlyEntities.js b/services/web/app/src/Features/Subscription/RecurlyEntities.js index a80db92321..c0f8eb944e 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEntities.js +++ b/services/web/app/src/Features/Subscription/RecurlyEntities.js @@ -6,7 +6,6 @@ const PlansLocator = require('./PlansLocator') const SubscriptionHelper = require('./SubscriptionHelper') const AI_ADD_ON_CODE = 'assistant' -const STANDALONE_AI_ADD_ON_CODES = ['assistant', 'assistant-annual'] class RecurlySubscription { /** @@ -24,7 +23,6 @@ class RecurlySubscription { * @param {number} props.total * @param {Date} props.periodStart * @param {Date} props.periodEnd - * @param {RecurlySubscriptionChange} [props.pendingChange] */ constructor(props) { this.id = props.id @@ -40,47 +38,12 @@ class RecurlySubscription { this.total = props.total this.periodStart = props.periodStart this.periodEnd = props.periodEnd - this.pendingChange = props.pendingChange ?? null } - /** - * Returns whether this subscription currently has the given add-on - * - * @param {string} code - * @return {boolean} - */ hasAddOn(code) { return this.addOns.some(addOn => addOn.code === code) } - /** - * Returns whether this subscription is a standalone AI add-on subscription - * - * @return {boolean} - */ - isStandaloneAiAddOn() { - return isStandaloneAiAddOnPlanCode(this.planCode) - } - - /** - * Returns whether this subcription will have the given add-on next billing - * period. - * - * There are two cases: either the subscription already has the add-on and - * won't change next period, or the subscription will change next period and - * the change includes the add-on. - * - * @param {string} code - * @return {boolean} - */ - hasAddOnNextPeriod(code) { - if (this.pendingChange != null) { - return this.pendingChange.nextAddOns.some(addOn => addOn.code === code) - } else { - return this.hasAddOn(code) - } - } - /** * Change this subscription's plan * @@ -97,31 +60,16 @@ class RecurlySubscription { if (newPlan == null) { throw new OError('Unable to find plan in settings', { planCode }) } - const shouldChangeAtTermEnd = SubscriptionHelper.shouldPlanChangeAtTermEnd( + const changeAtTermEnd = SubscriptionHelper.shouldPlanChangeAtTermEnd( currentPlan, newPlan ) - - const changeRequest = new RecurlySubscriptionChangeRequest({ + const timeframe = changeAtTermEnd ? 'term_end' : 'now' + return new RecurlySubscriptionChangeRequest({ subscription: this, - timeframe: shouldChangeAtTermEnd ? 'term_end' : 'now', + timeframe, planCode, }) - - // Carry the AI add-on to the new plan if applicable - if ( - this.isStandaloneAiAddOn() || - (!shouldChangeAtTermEnd && this.hasAddOn(AI_ADD_ON_CODE)) || - (shouldChangeAtTermEnd && this.hasAddOnNextPeriod(AI_ADD_ON_CODE)) - ) { - const addOnUpdate = new RecurlySubscriptionAddOnUpdate({ - code: AI_ADD_ON_CODE, - quantity: 1, - }) - changeRequest.addOnUpdates = [addOnUpdate] - } - - return changeRequest } /** @@ -314,30 +262,6 @@ class RecurlyAddOn { } } -/** - * A plan configuration - */ -class RecurlyPlan { - /** - * @param {object} props - * @param {string} props.code - * @param {string} props.name - */ - constructor(props) { - this.code = props.code - this.name = props.name - } -} - -/** - * Returns whether the given plan code is a standalone AI plan - * - * @param {string} planCode - */ -function isStandaloneAiAddOnPlanCode(planCode) { - return STANDALONE_AI_ADD_ON_CODES.includes(planCode) -} - module.exports = { AI_ADD_ON_CODE, RecurlySubscription, @@ -348,6 +272,4 @@ module.exports = { PaypalPaymentMethod, CreditCardPaymentMethod, RecurlyAddOn, - RecurlyPlan, - isStandaloneAiAddOnPlanCode, } diff --git a/services/web/app/src/Features/Subscription/RecurlyWrapper.js b/services/web/app/src/Features/Subscription/RecurlyWrapper.js index 9660a8c5dd..3cd2d9ad0c 100644 --- a/services/web/app/src/Features/Subscription/RecurlyWrapper.js +++ b/services/web/app/src/Features/Subscription/RecurlyWrapper.js @@ -10,10 +10,6 @@ const Errors = require('../Errors/Errors') const SubscriptionErrors = require('./Errors') const { callbackify } = require('@overleaf/promise-utils') -/** - * @param accountId - * @param newEmail - */ async function updateAccountEmailAddress(accountId, newEmail) { const data = { email: newEmail, @@ -818,9 +814,6 @@ const promises = { } }, - /** - * @param xml - */ _parseXml(xml) { function convertDataTypes(data) { let key, value diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 4f6e3d4287..6982bea794 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -29,13 +29,9 @@ const HttpErrorHandler = require('../Errors/HttpErrorHandler') const { URLSearchParams } = require('url') const RecurlyClient = require('./RecurlyClient') const { AI_ADD_ON_CODE } = require('./RecurlyEntities') -const PlansLocator = require('./PlansLocator') /** - * @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview' * @import { SubscriptionChangePreview } from '../../../../types/subscription/subscription-change-preview' - * @import { RecurlySubscriptionChange } from './RecurlyEntities' - * @import { PaymentMethod } from './types' */ const groupPlanModalOptions = Settings.groupPlanModalOptions @@ -520,17 +516,38 @@ async function previewAddonPurchase(req, res) { ) /** @type {SubscriptionChangePreview} */ - const changePreview = makeChangePreview( - { + const changePreview = { + change: { type: 'add-on-purchase', addOn: { code: addOn.code, name: addOn.name, }, }, - subscriptionChange, - paymentMethod - ) + currency: subscription.currency, + immediateCharge: subscriptionChange.immediateCharge, + paymentMethod: paymentMethod.toString(), + nextInvoice: { + date: subscription.periodEnd.toISOString(), + plan: { + name: subscriptionChange.nextPlanName, + amount: subscriptionChange.nextPlanPrice, + }, + addOns: subscriptionChange.nextAddOns.map(addOn => ({ + code: addOn.code, + name: addOn.name, + quantity: addOn.quantity, + unitAmount: addOn.unitPrice, + amount: addOn.preTaxTotal, + })), + subtotal: subscriptionChange.subtotal, + tax: { + rate: subscription.taxRate, + amount: subscriptionChange.tax, + }, + total: subscriptionChange.total, + }, + } res.render('subscriptions/preview-change', { changePreview }) } @@ -602,31 +619,6 @@ async function removeAddon(req, res, next) { } } -async function previewSubscription(req, res, next) { - const planCode = req.query.planCode - if (!planCode) { - return HttpErrorHandler.notFound(req, res, 'Missing plan code') - } - const plan = await RecurlyClient.promises.getPlan(planCode) - const userId = SessionManager.getLoggedInUserId(req.session) - const subscriptionChange = - await SubscriptionHandler.promises.previewSubscriptionChange( - userId, - planCode - ) - const paymentMethod = await RecurlyClient.promises.getPaymentMethod(userId) - const changePreview = makeChangePreview( - { - type: 'premium-subscription', - plan: { code: plan.code, name: plan.name }, - }, - subscriptionChange, - paymentMethod - ) - - res.render('subscriptions/preview-change', { changePreview }) -} - function updateSubscription(req, res, next) { const origin = req && req.query ? req.query.origin : null const user = SessionManager.getSessionUser(req.session) @@ -895,54 +887,6 @@ async function getLatamCountryBannerDetails(req, res) { return latamCountryBannerDetails } -/** - * Build a subscription change preview for display purposes - * - * @param {SubscriptionChangeDescription} subscriptionChangeDescription A description of the change for the frontend - * @param {RecurlySubscriptionChange} subscriptionChange The subscription change object coming from Recurly - * @param {PaymentMethod} paymentMethod The payment method associated to the user - * @return {SubscriptionChangePreview} - */ -function makeChangePreview( - subscriptionChangeDescription, - subscriptionChange, - paymentMethod -) { - const subscription = subscriptionChange.subscription - const nextPlan = PlansLocator.findLocalPlanInSettings( - subscriptionChange.nextPlanCode - ) - return { - change: subscriptionChangeDescription, - currency: subscription.currency, - immediateCharge: subscriptionChange.immediateCharge, - paymentMethod: paymentMethod.toString(), - nextPlan: { - annual: nextPlan.annual ?? false, - }, - nextInvoice: { - date: subscription.periodEnd.toISOString(), - plan: { - name: subscriptionChange.nextPlanName, - amount: subscriptionChange.nextPlanPrice, - }, - addOns: subscriptionChange.nextAddOns.map(addOn => ({ - code: addOn.code, - name: addOn.name, - quantity: addOn.quantity, - unitAmount: addOn.unitPrice, - amount: addOn.preTaxTotal, - })), - subtotal: subscriptionChange.subtotal, - tax: { - rate: subscription.taxRate, - amount: subscriptionChange.tax, - }, - total: subscriptionChange.total, - }, - } -} - module.exports = { plansPage: expressify(plansPage), plansPageLightDesign: expressify(plansPageLightDesign), @@ -952,7 +896,6 @@ module.exports = { cancelSubscription, canceledSubscription: expressify(canceledSubscription), cancelV1Subscription, - previewSubscription: expressify(previewSubscription), updateSubscription, cancelPendingSubscriptionChange, updateAccountEmailAddress, diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index bd1a1476bb..9592c96412 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -65,26 +65,6 @@ async function createSubscription(user, subscriptionDetails, recurlyTokenIds) { ) } -/** - * Preview the effect of changing the subscription plan - * - * @param {string} userId - * @param {string} planCode - * @return {Promise} - */ -async function previewSubscriptionChange(userId, planCode) { - const recurlyId = await getSubscriptionRecurlyId(userId) - if (recurlyId == null) { - throw new NoRecurlySubscriptionError('Subscription not found', { userId }) - } - - const subscription = await RecurlyClient.promises.getSubscription(recurlyId) - const changeRequest = subscription?.getRequestForPlanChange(planCode) - const change = - await RecurlyClient.promises.previewSubscriptionChange(changeRequest) - return change -} - /** * @param user * @param planCode @@ -381,7 +361,6 @@ async function getSubscriptionRecurlyId(userId) { module.exports = { validateNoSubscriptionInRecurly: callbackify(validateNoSubscriptionInRecurly), createSubscription: callbackify(createSubscription), - previewSubscriptionChange: callbackify(previewSubscriptionChange), updateSubscription: callbackify(updateSubscription), cancelPendingSubscriptionChange: callbackify(cancelPendingSubscriptionChange), cancelSubscription: callbackify(cancelSubscription), @@ -395,7 +374,6 @@ module.exports = { promises: { validateNoSubscriptionInRecurly, createSubscription, - previewSubscriptionChange, updateSubscription, cancelPendingSubscriptionChange, cancelSubscription, diff --git a/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs b/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs index 3e7a585509..2305dc29de 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs @@ -121,12 +121,6 @@ export default { ) // user changes their account state - webRouter.get( - '/user/subscription/preview', - AuthenticationController.requireLogin(), - RateLimiterMiddleware.rateLimit(subscriptionRateLimiter), - SubscriptionController.previewSubscription - ) webRouter.post( '/user/subscription/update', AuthenticationController.requireLogin(), diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index fd93dae6e6..3f029cf750 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -66,7 +66,6 @@ "add_company_details": "", "add_email_address": "", "add_email_to_claim_features": "", - "add_error_assist_to_your_projects": "", "add_files": "", "add_more_collaborators": "", "add_more_editors": "", @@ -1485,7 +1484,6 @@ "submit_title": "", "subscribe": "", "subscribe_to_find_the_symbols_you_need_faster": "", - "subscribe_to_plan": "", "subscription": "", "subscription_admins_cannot_be_deleted": "", "subscription_canceled": "", diff --git a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx index 11da69fc7a..90ed5f6ab2 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx @@ -7,7 +7,11 @@ import { ExpiredSubscription } from './states/expired' import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context' import PersonalSubscriptionRecurlySyncEmail from './personal-subscription-recurly-sync-email' import OLNotification from '@/features/ui/components/ol/ol-notification' -import { isStandaloneAiPlanCode, AI_ADD_ON_CODE } from '../../data/add-on-codes' +import { + AI_STANDALONE_PLAN_CODE, + AI_STANDALONE_ANNUAL_PLAN_CODE, + AI_ADD_ON_CODE, +} from '../../data/add-on-codes' function PastDueSubscriptionAlert({ subscription, @@ -46,7 +50,11 @@ function PersonalSubscriptionStates({ addOn => addOn.addOnCode === AI_ADD_ON_CODE ) - const onAiStandalonePlan = isStandaloneAiPlanCode(subscription.planCode) + const onAiStandalonePlan = [ + AI_STANDALONE_PLAN_CODE, + AI_STANDALONE_ANNUAL_PLAN_CODE, + ].includes(subscription.planCode) + const planHasAi = onAiStandalonePlan || hasAiAddon if (state === 'active' && planHasAi) { diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-ai-addon.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-ai-addon.tsx index b257ca2754..c44a9fe656 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-ai-addon.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-ai-addon.tsx @@ -15,7 +15,8 @@ import { ADD_ON_NAME, AI_ADD_ON_CODE, AI_STANDALONE_PLAN_NAME, - isStandaloneAiPlanCode, + AI_STANDALONE_PLAN_CODE, + AI_STANDALONE_ANNUAL_PLAN_CODE, } from '../../../../data/add-on-codes' import { CancelSubscriptionButton } from './cancel-subscription-button' @@ -31,7 +32,10 @@ export function ActiveAiAddonSubscription({ useSubscriptionDashboardContext() if (showCancellation) return - const onStandalonePlan = isStandaloneAiPlanCode(subscription.planCode) + const onStandalonePlan = [ + AI_STANDALONE_PLAN_CODE, + AI_STANDALONE_ANNUAL_PLAN_CODE, + ].includes(subscription.planCode) const handlePlanChange = () => setModalIdShown('change-plan') diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal.tsx index 0eeb139c8b..34a8ccae99 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal.tsx @@ -14,8 +14,8 @@ import OLButton from '@/features/ui/components/ol/ol-button' import OLNotification from '@/features/ui/components/ol/ol-notification' import { AI_ADD_ON_CODE, + AI_STANDALONE_PLAN_CODE, ADD_ON_NAME, - isStandaloneAiPlanCode, } from '../../../../../../data/add-on-codes' import { cancelSubscriptionUrl, @@ -33,7 +33,7 @@ export function CancelAiAddOnModal() { if (!personalSubscription) return null - const onStandalone = isStandaloneAiPlanCode(personalSubscription.planCode) + const onStandalone = personalSubscription.planCode === AI_STANDALONE_PLAN_CODE const cancellationEndpoint = onStandalone ? cancelSubscriptionUrl diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index bcf51e7891..09a5824f96 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -31,17 +31,13 @@ function PreviewSubscriptionChange() {
- {preview.change.type === 'add-on-purchase' ? ( + {preview.change.type === 'add-on-purchase' && (

{t('add_add_on_to_your_plan', { addOnName: preview.change.addOn.name, })}

- ) : preview.change.type === 'premium-subscription' ? ( -

- {t('subscribe_to_plan', { planName: preview.change.plan.name })} -

- ) : null} + )} {payNowTask.isError && ( - - {preview.nextPlan.annual - ? t('total_per_year') - : t('total_per_month')} - + {t('total_per_month')} {formatCurrencyLocalized( preview.nextInvoice.total, diff --git a/services/web/frontend/js/features/subscription/data/add-on-codes.ts b/services/web/frontend/js/features/subscription/data/add-on-codes.ts index c13efaa720..cb32f1fa4c 100644 --- a/services/web/frontend/js/features/subscription/data/add-on-codes.ts +++ b/services/web/frontend/js/features/subscription/data/add-on-codes.ts @@ -3,8 +3,4 @@ export const AI_ADD_ON_CODE = 'assistant' // we dont want translations on plan or add-on names export const ADD_ON_NAME = "Error Assist" export const AI_STANDALONE_PLAN_NAME = "Overleaf Free" -export const AI_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual' - -export function isStandaloneAiPlanCode(planCode: string) { - return planCode === AI_STANDALONE_PLAN_CODE || planCode === AI_STANDALONE_ANNUAL_PLAN_CODE -} +export const AI_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual' \ No newline at end of file diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 31fee2a267..da85c29b7a 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -78,7 +78,6 @@ "add_email": "Add Email", "add_email_address": "Add email address", "add_email_to_claim_features": "Add an institutional email address to claim your features.", - "add_error_assist_to_your_projects": "Add Error Assist to your projects and get AI help to fix LaTeX errors faster.", "add_files": "Add Files", "add_more_collaborators": "Add more collaborators", "add_more_editors": "Add more editors", @@ -2062,7 +2061,6 @@ "submit_title": "Submit", "subscribe": "Subscribe", "subscribe_to_find_the_symbols_you_need_faster": "Subscribe to find the symbols you need faster", - "subscribe_to_plan": "Subscribe to __planName__", "subscription": "Subscription", "subscription_admin_panel": "admin panel", "subscription_admins_cannot_be_deleted": "You cannot delete your account while on a subscription. Please cancel your subscription and try again. If you keep seeing this message please contact us.", diff --git a/services/web/package.json b/services/web/package.json index d1ca76c39a..8f28101e1a 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -234,6 +234,7 @@ "@types/chai": "^4.3.0", "@types/dateformat": "^5.0.2", "@types/diff": "^5.0.9", + "uuid": "^9.0.1", "@types/dompurify": "^3.0.5", "@types/events": "^3.0.0", "@types/express": "^4.17.13", @@ -257,8 +258,8 @@ "@uppy/react": "^3.2.1", "@uppy/utils": "^5.7.0", "@uppy/xhr-upload": "^3.6.0", - "5to6-codemod": "^1.8.0", "abort-controller": "^3.0.0", + "5to6-codemod": "^1.8.0", "acorn": "^7.1.1", "acorn-walk": "^7.1.1", "algoliasearch": "^3.35.1", @@ -322,7 +323,6 @@ "mocha": "^10.2.0", "mocha-each": "^2.0.1", "mock-fs": "^5.1.2", - "nock": "^13.5.6", "nvd3": "^1.8.6", "overleaf-editor-core": "*", "pdfjs-dist": "4.6.82", @@ -363,7 +363,6 @@ "to-string-loader": "^1.2.0", "tty-browserify": "^0.0.1", "typescript": "^5.0.4", - "uuid": "^9.0.1", "w3c-keyname": "^2.2.8", "webpack": "^5.93.0", "webpack-assets-manifest": "^5.2.1", diff --git a/services/web/test/unit/src/Subscription/RecurlyClientTests.js b/services/web/test/unit/src/Subscription/RecurlyClientTests.js index 2267bf1c19..9e861fead3 100644 --- a/services/web/test/unit/src/Subscription/RecurlyClientTests.js +++ b/services/web/test/unit/src/Subscription/RecurlyClientTests.js @@ -3,7 +3,6 @@ const { expect } = require('chai') const recurly = require('recurly') const SandboxedModule = require('sandboxed-module') const { - RecurlySubscription, RecurlySubscriptionChangeRequest, RecurlySubscriptionAddOnUpdate, } = require('../../../../app/src/Features/Subscription/RecurlyEntities') @@ -36,7 +35,7 @@ describe('RecurlyClient', function () { preTaxTotal: 2, } - this.subscription = new RecurlySubscription({ + this.subscription = { id: 'subscription-id', userId: 'user-id', currency: 'EUR', @@ -50,7 +49,7 @@ describe('RecurlyClient', function () { total: 16.5, periodStart: new Date(), periodEnd: new Date(), - }) + } this.recurlySubscription = { uuid: this.subscription.id, diff --git a/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js b/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js index 7331118739..aed3951ed3 100644 --- a/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js +++ b/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js @@ -4,11 +4,9 @@ const SandboxedModule = require('sandboxed-module') const { expect } = require('chai') const Errors = require('../../../../app/src/Features/Subscription/Errors') const { - AI_ADD_ON_CODE, RecurlySubscriptionChangeRequest, RecurlySubscriptionChange, RecurlySubscription, - RecurlySubscriptionAddOnUpdate, } = require('../../../../app/src/Features/Subscription/RecurlyEntities') const MODULE_PATH = '../../../../app/src/Features/Subscription/RecurlyEntities' @@ -18,7 +16,6 @@ describe('RecurlyEntities', function () { beforeEach(function () { this.Settings = { plans: [ - { planCode: 'assistant-annual', price_in_cents: 5900 }, { planCode: 'cheap-plan', price_in_cents: 500 }, { planCode: 'regular-plan', price_in_cents: 1000 }, { planCode: 'premium-plan', price_in_cents: 2000 }, @@ -95,67 +92,6 @@ describe('RecurlyEntities', function () { }) ) }) - - it('preserves the AI add-on on upgrades', function () { - const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities - this.addOn.code = AI_ADD_ON_CODE - const changeRequest = - this.subscription.getRequestForPlanChange('premium-plan') - expect(changeRequest).to.deep.equal( - new RecurlySubscriptionChangeRequest({ - subscription: this.subscription, - timeframe: 'now', - planCode: 'premium-plan', - addOnUpdates: [ - new RecurlySubscriptionAddOnUpdate({ - code: AI_ADD_ON_CODE, - quantity: 1, - }), - ], - }) - ) - }) - - it('preserves the AI add-on on downgrades', function () { - const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities - this.addOn.code = AI_ADD_ON_CODE - const changeRequest = - this.subscription.getRequestForPlanChange('cheap-plan') - expect(changeRequest).to.deep.equal( - new RecurlySubscriptionChangeRequest({ - subscription: this.subscription, - timeframe: 'term_end', - planCode: 'cheap-plan', - addOnUpdates: [ - new RecurlySubscriptionAddOnUpdate({ - code: AI_ADD_ON_CODE, - quantity: 1, - }), - ], - }) - ) - }) - - it('preserves the AI add-on on upgrades from the standalone AI plan', function () { - const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities - this.subscription.planCode = 'assistant-annual' - this.subscription.addOns = [] - const changeRequest = - this.subscription.getRequestForPlanChange('cheap-plan') - expect(changeRequest).to.deep.equal( - new RecurlySubscriptionChangeRequest({ - subscription: this.subscription, - timeframe: 'term_end', - planCode: 'cheap-plan', - addOnUpdates: [ - new RecurlySubscriptionAddOnUpdate({ - code: AI_ADD_ON_CODE, - quantity: 1, - }), - ], - }) - ) - }) }) describe('getRequestForAddOnPurchase()', function () { diff --git a/services/web/types/subscription/subscription-change-preview.ts b/services/web/types/subscription/subscription-change-preview.ts index dab0970151..4b0b83bbb5 100644 --- a/services/web/types/subscription/subscription-change-preview.ts +++ b/services/web/types/subscription/subscription-change-preview.ts @@ -1,11 +1,8 @@ export type SubscriptionChangePreview = { - change: SubscriptionChangeDescription + change: SubscriptionChange currency: string paymentMethod: string immediateCharge: number - nextPlan: { - annual: boolean - } nextInvoice: { date: string plan: { @@ -30,7 +27,7 @@ type AddOn = { amount: number } -export type SubscriptionChangeDescription = AddOnPurchase | PremiumSubscription +type SubscriptionChange = AddOnPurchase type AddOnPurchase = { type: 'add-on-purchase' @@ -39,11 +36,3 @@ type AddOnPurchase = { name: string } } - -type PremiumSubscription = { - type: 'premium-subscription' - plan: { - code: string - name: string - } -}