From eb5417fad5f7424692e7305ff7c44b389dfd72ba Mon Sep 17 00:00:00 2001 From: Liangjun Song <146005915+adai26@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:42:02 +0000 Subject: [PATCH] Merge pull request #23462 from overleaf/ls-update-pricing-logic-for-small-educational-plans Update pricing logic for small educational plans GitOrigin-RevId: 0051f238ce50b2067b7dc75d08f55dc1c7ac3502 --- .../Subscription/SubscriptionGroupHandler.js | 25 +++++++- .../SubscriptionGroupHandlerTests.js | 60 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js index 02833c20cc..63e23816b3 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js @@ -143,8 +143,12 @@ async function _addSeatsSubscriptionChange(userId, adding) { .additional_license_legacy_price_in_cents if ( - planPriceInCents / 100 > recurlySubscription.planPrice && - legacyUnitPriceInCents > 0 + _shouldUseLegacyPricing( + recurlySubscription.planPrice, + planPriceInCents / 100, + usage, + size + ) ) { unitPrice = legacyUnitPriceInCents / 100 } @@ -163,6 +167,23 @@ async function _addSeatsSubscriptionChange(userId, adding) { } } +function _shouldUseLegacyPricing( + actualPlanPrice, + currentPlanPrice, + usage, + size +) { + // For small educational groups (5 or fewer members) + // 2025 pricing is cheaper than legacy pricing + if (size <= 5 && usage === 'educational') { + return currentPlanPrice < actualPlanPrice + } + + // For all other scenarios + // 2025 pricing is more expensive than legacy pricing + return currentPlanPrice > actualPlanPrice +} + async function previewAddSeatsSubscriptionChange(userId, adding) { const { changeRequest, currentAddonQuantity } = await _addSeatsSubscriptionChange(userId, adding) diff --git a/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js index 4e909a687b..b5024b99f3 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js @@ -143,6 +143,16 @@ describe('SubscriptionGroupHandler', function () { }, }, }, + educational: { + collaborator: { + USD: { + 5: { + price_in_cents: 10000, + additional_license_legacy_price_in_cents: 5000, + }, + }, + }, + }, } this.Handler = SandboxedModule.require(modulePath, { @@ -509,6 +519,56 @@ describe('SubscriptionGroupHandler', function () { ) .should.equal(true) }) + + it('should return the subscription change preview with legacy add-on price for small educational group', async function () { + this.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({ + ...this.localPlanInSettings, + planCode: 'group_collaborator_5_educational', + canUseFlexibleLicensing: true, + }) + this.recurlySubscription.planPrice = + this.GroupPlansData.enterprise.collaborator.USD[5].price_in_cents / + 100 + + 1 + + preview = + await this.Handler.promises.previewAddSeatsSubscriptionChange( + this.adminUser_id, + this.adding + ) + this.recurlySubscription.getRequestForAddOnPurchase + .calledWithExactly( + this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + this.adding, + this.GroupPlansData.enterprise.collaborator.USD[5] + .additional_license_legacy_price_in_cents / 100 + ) + .should.equal(true) + }) + + it('should return the subscription change preview with non-legacy add-on price for small educational group', async function () { + this.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({ + ...this.localPlanInSettings, + planCode: 'group_collaborator_5_educational', + canUseFlexibleLicensing: true, + }) + this.recurlySubscription.planPrice = + this.GroupPlansData.enterprise.collaborator.USD[5].price_in_cents / + 100 + + preview = + await this.Handler.promises.previewAddSeatsSubscriptionChange( + this.adminUser_id, + this.adding + ) + this.recurlySubscription.getRequestForAddOnPurchase + .calledWithExactly( + this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + this.adding, + undefined + ) + .should.equal(true) + }) }) }) })