From e6d75ad47b3398db643a8def3d8758fa521fcaf6 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Thu, 12 Jun 2025 13:19:42 +0200 Subject: [PATCH] Check if AI assist standalone plan is used in shouldPlanChangeAtTermEnd (#26272) GitOrigin-RevId: d6737ea28071d565109dba695876b6fbf3f5daa2 --- .../src/Features/Subscription/PaymentProviderEntities.js | 5 ++++- .../app/src/Features/Subscription/SubscriptionHelper.js | 9 +++++++++ .../unit/src/Subscription/PaymentProviderEntitiesTest.js | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js index 6fe8638389..064dbfcf29 100644 --- a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js +++ b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js @@ -8,7 +8,8 @@ const OError = require('@overleaf/o-error') const { DuplicateAddOnError, AddOnNotPresentError } = require('./Errors') const PlansLocator = require('./PlansLocator') -const SubscriptionHelper = require('./SubscriptionHelper') + +let SubscriptionHelper = null // Work around circular import (loaded at the bottom of the file) const AI_ADD_ON_CODE = 'assistant' const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license' @@ -636,3 +637,5 @@ module.exports = { subscriptionChangeIsAiAssistUpgrade, PaymentProviderImmediateCharge, } + +SubscriptionHelper = require('./SubscriptionHelper') diff --git a/services/web/app/src/Features/Subscription/SubscriptionHelper.js b/services/web/app/src/Features/Subscription/SubscriptionHelper.js index d409f47530..059314723a 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHelper.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHelper.js @@ -1,11 +1,20 @@ const { formatCurrency } = require('../../util/currency') const GroupPlansData = require('./GroupPlansData') +const { isStandaloneAiAddOnPlanCode } = require('./PaymentProviderEntities') /** * If the user changes to a less expensive plan, we shouldn't apply the change immediately. * This is to avoid unintended/artifical credits on users Recurly accounts. */ function shouldPlanChangeAtTermEnd(oldPlan, newPlan) { + if ( + oldPlan.annual === newPlan.annual && + isStandaloneAiAddOnPlanCode(oldPlan.planCode) && + !isStandaloneAiAddOnPlanCode(newPlan.planCode) + ) { + // changing from an standalone AI add-on plan to a non-AI plan should not be considered a downgrade + return false + } return oldPlan.price_in_cents > newPlan.price_in_cents } diff --git a/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js b/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js index c6593da28d..d19e8808a6 100644 --- a/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js +++ b/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js @@ -11,6 +11,7 @@ const { PaymentProviderSubscription, PaymentProviderSubscriptionAddOnUpdate, } = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') +const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper') const MODULE_PATH = '../../../../app/src/Features/Subscription/PaymentProviderEntities' @@ -32,6 +33,7 @@ describe('PaymentProviderEntities', function () { requires: { '@overleaf/settings': this.Settings, './Errors': Errors, + './SubscriptionHelper': SubscriptionHelper, }, }) }) @@ -154,7 +156,7 @@ describe('PaymentProviderEntities', function () { expect(changeRequest).to.deep.equal( new PaymentProviderSubscriptionChangeRequest({ subscription: this.subscription, - timeframe: 'term_end', + timeframe: 'now', planCode: 'cheap-plan', addOnUpdates: [ new PaymentProviderSubscriptionAddOnUpdate({