Merge pull request #23314 from overleaf/ls-update-group-plan-upgrade-mapping

Update group plan upgrade mapping

GitOrigin-RevId: aca3d986477dbcf0561635dfd771413a2ba1ef15
This commit is contained in:
ilkin-overleaf
2025-02-04 13:05:37 +02:00
committed by Copybot
parent a869fe75ae
commit be22dbc8e1
6 changed files with 67 additions and 56 deletions

View File

@@ -227,29 +227,16 @@ class RecurlySubscription {
* Upgrade group plan with the plan code provided
*
* @param {string} newPlanCode
* @param {number} membersLimit
* @return {RecurlySubscriptionChangeRequest}
*/
getRequestForGroupPlanUpgrade(newPlanCode, membersLimit) {
getRequestForGroupPlanUpgrade(newPlanCode) {
// Ensure all the existing add-ons are added to the new plan
// Except for the additional license, which will be added below
const addOns = this.addOns
.filter(addOn => addOn.code !== 'additional-license')
.map(
addOn =>
new RecurlySubscriptionAddOnUpdate({
code: addOn.code,
quantity: addOn.quantity,
})
)
// Get the number of licenses from the membersLimit field in the Subscription model
// This is necessary because legacy group plans do not fully use add-ons to represent seats
addOns.push(
new RecurlySubscriptionAddOnUpdate({
code: 'additional-license',
quantity: membersLimit,
})
const addOns = this.addOns.map(
addOn =>
new RecurlySubscriptionAddOnUpdate({
code: addOn.code,
quantity: addOn.quantity,
})
)
return new RecurlySubscriptionChangeRequest({

View File

@@ -683,6 +683,31 @@ async function getLatamCountryBannerDetails(req, res) {
return latamCountryBannerDetails
}
/**
* There are two sets of group plans: legacy plans and consolidated plans,
* and their naming conventions differ.
* This helper method computes the name of legacy group plans to ensure
* consistency with the naming of consolidated group plans.
*
* @param {string} planName
* @param {string} planCode
* @return {string}
*/
function getPlanNameForDisplay(planName, planCode) {
const match = planCode.match(
/^group_(collaborator|professional)_\d+_(enterprise|educational)$/
)
if (!match) return planName
const [, type, category] = match
const prefix = type === 'collaborator' ? 'Standard' : 'Professional'
const suffix = category === 'educational' ? ' Educational' : ''
return `Overleaf ${prefix} Group${suffix}`
}
/**
* Build a subscription change preview for display purposes
*
@@ -711,7 +736,10 @@ function makeChangePreview(
nextInvoice: {
date: subscription.periodEnd.toISOString(),
plan: {
name: subscriptionChange.nextPlanName,
name: getPlanNameForDisplay(
subscriptionChange.nextPlanName,
subscriptionChange.nextPlanCode
),
amount: subscriptionChange.nextPlanPrice,
},
addOns: subscriptionChange.nextAddOns.map(addOn => ({
@@ -755,4 +783,5 @@ module.exports = {
makeChangePreview,
getRecommendedCurrency,
getLatamCountryBannerDetails,
getPlanNameForDisplay,
}

View File

@@ -186,9 +186,7 @@ async function _getUpgradeTargetPlanCodeMaybeThrow(subscription) {
throw new Error('Not eligible for group plan upgrade')
}
return subscription.planCode.includes('educational')
? 'group_professional_educational'
: 'group_professional'
return subscription.planCode.replace('collaborator', 'professional')
}
async function _getGroupPlanUpgradeChangeRequest(ownerId) {
@@ -196,26 +194,11 @@ async function _getGroupPlanUpgradeChangeRequest(ownerId) {
await SubscriptionLocator.promises.getUsersSubscription(ownerId)
const newPlanCode = await _getUpgradeTargetPlanCodeMaybeThrow(olSubscription)
const recurlySubscription = await RecurlyClient.promises.getSubscription(
olSubscription.recurlySubscription_id
)
return recurlySubscription.getRequestForGroupPlanUpgrade(
newPlanCode,
olSubscription.membersLimit
)
}
function _getPlanNameForDisplay(subscription) {
if (/^group_collaborator_\d+_enterprise$/.test(subscription.planCode)) {
return 'Overleaf Standard Group'
}
if (/^group_collaborator_\d+_educational$/.test(subscription.planCode)) {
return 'Overleaf Standard Group Educational'
}
return subscription.planName
return recurlySubscription.getRequestForGroupPlanUpgrade(newPlanCode)
}
async function getGroupPlanUpgradePreview(ownerId) {
@@ -227,7 +210,10 @@ async function getGroupPlanUpgradePreview(ownerId) {
{
type: 'group-plan-upgrade',
prevPlan: {
name: _getPlanNameForDisplay(subscriptionChange.subscription),
name: SubscriptionController.getPlanNameForDisplay(
subscriptionChange.subscription.planName,
subscriptionChange.subscription.planCode
),
},
},
subscriptionChange,

View File

@@ -10,14 +10,22 @@ export const LICENSE_ADD_ON = 'additional-license'
function UpgradeSubscriptionPlanDetails() {
const { t } = useTranslation()
const preview = getMeta('ol-subscriptionChangePreview')
const totalLicenses = getMeta('ol-totalLicenses')
const licenseUnitPrice = useMemo(
() =>
preview.nextInvoice.addOns.filter(
addOn => addOn.code === LICENSE_ADD_ON
)[0].unitAmount,
[preview]
)
const licenseUnitPrice = useMemo(() => {
const additionalLicenseAddOn = preview.nextInvoice.addOns.filter(
addOn => addOn.code === LICENSE_ADD_ON
)
// Legacy plans might not have additional-license add-on.
// Hence we need to compute unit price
return additionalLicenseAddOn.length > 0
? additionalLicenseAddOn[0].unitAmount
: preview.nextInvoice.plan.amount / totalLicenses
}, [
preview.nextInvoice.addOns,
preview.nextInvoice.plan.amount,
totalLicenses,
])
return (
<Card

View File

@@ -280,19 +280,13 @@ describe('RecurlyEntities', function () {
describe('getRequestForGroupPlanUpgrade()', function () {
it('returns a correct change request', function () {
const changeRequest = this.subscription.getRequestForGroupPlanUpgrade(
'test_plan_code',
10
)
const changeRequest =
this.subscription.getRequestForGroupPlanUpgrade('test_plan_code')
const addOns = [
new RecurlySubscriptionAddOnUpdate({
code: 'add-on-code',
quantity: 1,
}),
new RecurlySubscriptionAddOnUpdate({
code: 'additional-license',
quantity: 10,
}),
]
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({

View File

@@ -71,6 +71,7 @@ describe('SubscriptionGroupHandler', function () {
this.SubscriptionController = {
makeChangePreview: sinon.stub().resolves(this.changePreview),
getPlanNameForDisplay: sinon.stub().resolves(),
}
this.SubscriptionUpdater = {
@@ -512,6 +513,9 @@ describe('SubscriptionGroupHandler', function () {
.stub()
.resolves({ groupPlan: true, planCode: 'group_collaborator' })
await this.Handler.promises.upgradeGroupPlan(this.user_id)
this.recurlySubscription.getRequestForGroupPlanUpgrade
.calledWith('group_professional')
.should.equal(true)
this.RecurlyClient.promises.applySubscriptionChangeRequest
.calledWith(this.changeRequest)
.should.equal(true)
@@ -528,6 +532,9 @@ describe('SubscriptionGroupHandler', function () {
planCode: 'group_collaborator_10_educational',
})
await this.Handler.promises.upgradeGroupPlan(this.user_id)
this.recurlySubscription.getRequestForGroupPlanUpgrade
.calledWith('group_professional_10_educational')
.should.equal(true)
this.RecurlyClient.promises.applySubscriptionChangeRequest
.calledWith(this.changeRequest)
.should.equal(true)