From a9e47f043ae6625faa0bbdd51eb8655fd0fa47fa Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Wed, 9 Jul 2025 15:59:42 +0200 Subject: [PATCH] Move AI related functions from PaymentProviderEntities to AiHelper (#26956) * Move AI related functions from PaymentProviderEntities to AiHelper * added @ts-check GitOrigin-RevId: 8c8eec334b40a7f8f8533f6d5194f428112f68f9 --- .../src/Features/Project/ProjectController.js | 4 +- .../app/src/Features/Subscription/AiHelper.js | 46 +++++++++++++++++++ .../Features/Subscription/FeaturesUpdater.js | 2 +- .../Subscription/PaymentProviderEntities.js | 43 +---------------- .../Subscription/RecurlyEventHandler.js | 2 +- .../Subscription/SubscriptionController.js | 11 ++--- .../Subscription/SubscriptionHelper.js | 2 +- .../Subscription/SubscriptionLocator.js | 5 +- .../SubscriptionViewModelBuilder.js | 6 +-- .../src/Subscription/FeaturesUpdaterTests.js | 2 +- .../PaymentProviderEntitiesTest.js | 4 +- .../SubscriptionControllerTests.js | 2 +- 12 files changed, 65 insertions(+), 64 deletions(-) create mode 100644 services/web/app/src/Features/Subscription/AiHelper.js diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index fc65da7b44..4075b06bb7 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -46,9 +46,7 @@ const TutorialHandler = require('../Tutorial/TutorialHandler') const UserUpdater = require('../User/UserUpdater') const Modules = require('../../infrastructure/Modules') const UserGetter = require('../User/UserGetter') -const { - isStandaloneAiAddOnPlanCode, -} = require('../Subscription/PaymentProviderEntities') +const { isStandaloneAiAddOnPlanCode } = require('../Subscription/AiHelper') const SubscriptionController = require('../Subscription/SubscriptionController.js') const { formatCurrency } = require('../../util/currency') diff --git a/services/web/app/src/Features/Subscription/AiHelper.js b/services/web/app/src/Features/Subscription/AiHelper.js new file mode 100644 index 0000000000..3a383f7b07 --- /dev/null +++ b/services/web/app/src/Features/Subscription/AiHelper.js @@ -0,0 +1,46 @@ +// @ts-check +// Initially, this functions lived in PaymentProviderEntities.js, +// but it was moved to this file to prevent circular dependency issue + +const AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE = 'assistant' +const AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual' +const AI_ADD_ON_CODE = 'assistant' + +/** + * Returns whether the given plan code is a standalone AI plan + * + * @param {string} planCode + * @return {boolean} + */ +function isStandaloneAiAddOnPlanCode(planCode) { + return ( + planCode === AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE || + planCode === AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE + ) +} + +/** + * Returns whether subscription change will have have the ai bundle once the change is processed + * + * @param {Object} subscriptionChange The subscription change object coming from payment provider + * type should be PaymentProviderSubscriptionChange but if imported here, it creates a circular dependency + * TODO: fix this when moved to es modules + * + * @return {boolean} + */ +function subscriptionChangeIsAiAssistUpgrade(subscriptionChange) { + return Boolean( + isStandaloneAiAddOnPlanCode(subscriptionChange.nextPlanCode) || + subscriptionChange.nextAddOns?.some( + addOn => addOn.code === AI_ADD_ON_CODE + ) + ) +} + +module.exports = { + AI_ADD_ON_CODE, + AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, + AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE, + isStandaloneAiAddOnPlanCode, + subscriptionChangeIsAiAssistUpgrade, +} diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index 16413c501c..c9ecea4dbb 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -15,7 +15,7 @@ const UserGetter = require('../User/UserGetter') const AnalyticsManager = require('../Analytics/AnalyticsManager') const Queues = require('../../infrastructure/Queues') const Modules = require('../../infrastructure/Modules') -const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities') +const { AI_ADD_ON_CODE } = require('./AiHelper') /** * Enqueue a job for refreshing features for the given user diff --git a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js index 4efecba0f5..c8c09d5113 100644 --- a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js +++ b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js @@ -9,13 +9,9 @@ const OError = require('@overleaf/o-error') const { DuplicateAddOnError, AddOnNotPresentError } = require('./Errors') const PlansLocator = require('./PlansLocator') - -let SubscriptionHelper = null // Work around circular import (loaded at the bottom of the file) - +const SubscriptionHelper = require('./SubscriptionHelper') +const { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } = require('./AiHelper') const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license' -const AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE = 'assistant' -const AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual' -const AI_ADD_ON_CODE = 'assistant' class PaymentProviderSubscription { /** @@ -588,18 +584,6 @@ class PaymentProviderAccount { } } -/** - * Returns whether the given plan code is a standalone AI plan - * - * @param {string} planCode - */ -function isStandaloneAiAddOnPlanCode(planCode) { - return ( - planCode === AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE || - planCode === AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE - ) -} - /** * Returns whether the given plan code is a group plan * @@ -609,27 +593,8 @@ function isGroupPlanCode(planCode) { return planCode.includes('group') } -/** - * Returns whether subscription change will have have the ai bundle once the change is processed - * - * @param {PaymentProviderSubscriptionChange} subscriptionChange The subscription change object coming from payment provider - * - * @return {boolean} - */ -function subscriptionChangeIsAiAssistUpgrade(subscriptionChange) { - return Boolean( - isStandaloneAiAddOnPlanCode(subscriptionChange.nextPlanCode) || - subscriptionChange.nextAddOns?.some( - addOn => addOn.code === AI_ADD_ON_CODE - ) - ) -} - module.exports = { - AI_ADD_ON_CODE, MEMBERS_LIMIT_ADD_ON_CODE, - AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, - AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE, PaymentProviderSubscription, PaymentProviderSubscriptionAddOn, PaymentProviderSubscriptionChange, @@ -643,9 +608,5 @@ module.exports = { PaymentProviderCoupon, PaymentProviderAccount, isGroupPlanCode, - isStandaloneAiAddOnPlanCode, - subscriptionChangeIsAiAssistUpgrade, PaymentProviderImmediateCharge, } - -SubscriptionHelper = require('./SubscriptionHelper') diff --git a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js index 51314b4b31..c131ee10ec 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js +++ b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js @@ -1,7 +1,7 @@ const SplitTestHandler = require('../SplitTests/SplitTestHandler') const AnalyticsManager = require('../Analytics/AnalyticsManager') const SubscriptionEmailHandler = require('./SubscriptionEmailHandler') -const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities') +const { AI_ADD_ON_CODE } = require('./AiHelper') const { ObjectId } = require('mongodb-legacy') const INVOICE_SUBSCRIPTION_LIMIT = 10 diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 0cf1dd959c..fe79f3caed 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -27,9 +27,11 @@ const Modules = require('../../infrastructure/Modules') const async = require('async') const HttpErrorHandler = require('../Errors/HttpErrorHandler') const RecurlyClient = require('./RecurlyClient') -const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities') +const { + AI_ADD_ON_CODE, + subscriptionChangeIsAiAssistUpgrade, +} = require('./AiHelper') const PlansLocator = require('./PlansLocator') -const PaymentProviderEntities = require('./PaymentProviderEntities') const { User } = require('../../models/User') const UserGetter = require('../User/UserGetter') const PermissionsManager = require('../Authorization/PermissionsManager') @@ -377,10 +379,7 @@ async function previewAddonPurchase(req, res) { const { isPremium: hasAiAssistViaWritefull } = await UserGetter.promises.getWritefullData(userId) - const isAiUpgrade = - PaymentProviderEntities.subscriptionChangeIsAiAssistUpgrade( - subscriptionChange - ) + const isAiUpgrade = subscriptionChangeIsAiAssistUpgrade(subscriptionChange) if (hasAiAssistViaWritefull && isAiUpgrade) { return res.redirect( '/user/subscription?redirect-reason=writefull-entitled' diff --git a/services/web/app/src/Features/Subscription/SubscriptionHelper.js b/services/web/app/src/Features/Subscription/SubscriptionHelper.js index 0a93fa3479..35c3b4c132 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHelper.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHelper.js @@ -1,6 +1,6 @@ const { formatCurrency } = require('../../util/currency') const GroupPlansData = require('./GroupPlansData') -const { isStandaloneAiAddOnPlanCode } = require('./PaymentProviderEntities') +const { isStandaloneAiAddOnPlanCode } = require('./AiHelper') /** * If the user changes to a less expensive plan, we shouldn't apply the change immediately. diff --git a/services/web/app/src/Features/Subscription/SubscriptionLocator.js b/services/web/app/src/Features/Subscription/SubscriptionLocator.js index c0c107eecf..a8db8979f8 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionLocator.js +++ b/services/web/app/src/Features/Subscription/SubscriptionLocator.js @@ -6,10 +6,7 @@ const { callbackifyAll } = require('@overleaf/promise-utils') const { Subscription } = require('../../models/Subscription') const { DeletedSubscription } = require('../../models/DeletedSubscription') const logger = require('@overleaf/logger') -const { - AI_ADD_ON_CODE, - isStandaloneAiAddOnPlanCode, -} = require('./PaymentProviderEntities') +const { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } = require('./AiHelper') require('./GroupPlansData') // make sure dynamic group plans are loaded const SubscriptionLocator = { diff --git a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js index 3681975a38..5252abc070 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js +++ b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js @@ -1,10 +1,8 @@ // ts-check const Settings = require('@overleaf/settings') const PlansLocator = require('./PlansLocator') -const { - isStandaloneAiAddOnPlanCode, - MEMBERS_LIMIT_ADD_ON_CODE, -} = require('./PaymentProviderEntities') +const { isStandaloneAiAddOnPlanCode } = require('./AiHelper') +const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./PaymentProviderEntities') const SubscriptionFormatters = require('./SubscriptionFormatters') const SubscriptionLocator = require('./SubscriptionLocator') const InstitutionsGetter = require('../Institutions/InstitutionsGetter') diff --git a/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js b/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js index 8cdf313395..dccbff476c 100644 --- a/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js +++ b/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js @@ -4,7 +4,7 @@ const sinon = require('sinon') const { ObjectId } = require('mongodb-legacy') const { AI_ADD_ON_CODE, -} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') +} = require('../../../../app/src/Features/Subscription/AiHelper') const MODULE_PATH = '../../../../app/src/Features/Subscription/FeaturesUpdater' diff --git a/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js b/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js index 07c401dfb8..2cb7037e67 100644 --- a/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js +++ b/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js @@ -4,13 +4,15 @@ const SandboxedModule = require('sandboxed-module') const { expect } = require('chai') const Errors = require('../../../../app/src/Features/Subscription/Errors') const { - AI_ADD_ON_CODE, PaymentProviderSubscriptionChangeRequest, PaymentProviderSubscriptionUpdateRequest, PaymentProviderSubscriptionChange, PaymentProviderSubscription, PaymentProviderSubscriptionAddOnUpdate, } = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') +const { + AI_ADD_ON_CODE, +} = require('../../../../app/src/Features/Subscription/AiHelper') const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper') const MODULE_PATH = diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 61f9debc8d..24ce1bce8b 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -9,7 +9,7 @@ const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Er const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper') const { AI_ADD_ON_CODE, -} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') +} = require('../../../../app/src/Features/Subscription/AiHelper') const mockSubscriptions = { 'subscription-123-active': {