diff --git a/services/web/app/src/Features/Subscription/RecurlyEntities.js b/services/web/app/src/Features/Subscription/RecurlyEntities.js index 237bfc442e..adfb876157 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEntities.js +++ b/services/web/app/src/Features/Subscription/RecurlyEntities.js @@ -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({ diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 5d23ce5181..74f53f20be 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -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, } diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js index 9574f7a1d1..51b70f2d8e 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js @@ -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, diff --git a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx index 1236e0414b..1acdcbfb9b 100644 --- a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx +++ b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx @@ -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 (