diff --git a/services/web/app/src/Features/Subscription/GroupPlansData.js b/services/web/app/src/Features/Subscription/GroupPlansData.js index 144d0d811b..660abd51b3 100644 --- a/services/web/app/src/Features/Subscription/GroupPlansData.js +++ b/services/web/app/src/Features/Subscription/GroupPlansData.js @@ -42,7 +42,7 @@ for (const [usage, planData] of Object.entries(groups)) { // Generate plans in settings for (const size of sizes) { - Settings.plans.push({ + let plan = { planCode: `group_${planCode}_${size}_${usage}`, name: `${ Settings.appName @@ -55,8 +55,19 @@ for (const [usage, planData] of Object.entries(groups)) { features: Settings.features[planCode], groupPlan: true, membersLimit: parseInt(size), - membersLimitAddOn: 'additional-license', - }) + // Unlock flexible licensing for all plans + canUseFlexibleLicensing: true, + } + + // Add the `membersLimitAddOn` only to group plans of 5 or greater size + if (size >= 5) { + plan = { + ...plan, + membersLimitAddOn: 'additional-license', + } + } + + Settings.plans.push(plan) } } } diff --git a/services/web/app/src/Features/Subscription/RecurlyClient.js b/services/web/app/src/Features/Subscription/RecurlyClient.js index 593b0286ab..bbe7fe26bc 100644 --- a/services/web/app/src/Features/Subscription/RecurlyClient.js +++ b/services/web/app/src/Features/Subscription/RecurlyClient.js @@ -247,7 +247,8 @@ function subscriptionFromApi(apiSubscription) { apiSubscription.total == null || apiSubscription.currency == null || apiSubscription.currentPeriodStartedAt == null || - apiSubscription.currentPeriodEndsAt == null + apiSubscription.currentPeriodEndsAt == null || + apiSubscription.createdAt == null ) { throw new OError('Invalid Recurly subscription', { subscription: apiSubscription, @@ -268,6 +269,7 @@ function subscriptionFromApi(apiSubscription) { currency: apiSubscription.currency, periodStart: apiSubscription.currentPeriodStartedAt, periodEnd: apiSubscription.currentPeriodEndsAt, + createdAt: apiSubscription.createdAt, }) if (apiSubscription.pendingChange != null) { diff --git a/services/web/app/src/Features/Subscription/RecurlyEntities.js b/services/web/app/src/Features/Subscription/RecurlyEntities.js index 18288c72d8..237bfc442e 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEntities.js +++ b/services/web/app/src/Features/Subscription/RecurlyEntities.js @@ -6,6 +6,7 @@ const PlansLocator = require('./PlansLocator') const SubscriptionHelper = require('./SubscriptionHelper') const AI_ADD_ON_CODE = 'assistant' +const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license' const STANDALONE_AI_ADD_ON_CODES = ['assistant', 'assistant-annual'] class RecurlySubscription { @@ -24,6 +25,7 @@ class RecurlySubscription { * @param {number} props.total * @param {Date} props.periodStart * @param {Date} props.periodEnd + * @param {Date} props.createdAt * @param {RecurlySubscriptionChange} [props.pendingChange] */ constructor(props) { @@ -40,6 +42,7 @@ class RecurlySubscription { this.total = props.total this.periodStart = props.periodStart this.periodEnd = props.periodEnd + this.createdAt = props.createdAt this.pendingChange = props.pendingChange ?? null } @@ -129,12 +132,13 @@ class RecurlySubscription { * * @param {string} code * @param {number} [quantity] + * @param {number} [unitPrice] * @return {RecurlySubscriptionChangeRequest} - the change request to send to * Recurly * * @throws {DuplicateAddOnError} if the add-on is already present on the subscription */ - getRequestForAddOnPurchase(code, quantity = 1) { + getRequestForAddOnPurchase(code, quantity = 1, unitPrice) { if (this.hasAddOn(code)) { throw new DuplicateAddOnError('Subscription already has add-on', { subscriptionId: this.id, @@ -143,7 +147,9 @@ class RecurlySubscription { } const addOnUpdates = this.addOns.map(addOn => addOn.toAddOnUpdate()) - addOnUpdates.push(new RecurlySubscriptionAddOnUpdate({ code, quantity })) + addOnUpdates.push( + new RecurlySubscriptionAddOnUpdate({ code, quantity, unitPrice }) + ) return new RecurlySubscriptionChangeRequest({ subscription: this, timeframe: 'now', @@ -431,6 +437,7 @@ function isStandaloneAiAddOnPlanCode(planCode) { module.exports = { AI_ADD_ON_CODE, + MEMBERS_LIMIT_ADD_ON_CODE, RecurlySubscription, RecurlySubscriptionAddOn, RecurlySubscriptionChange, diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs b/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs index 53de39071f..efa63ab11c 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs @@ -128,6 +128,7 @@ async function addSeatsToGroupSubscription(req, res) { req ) await SubscriptionGroupHandler.promises.ensureFlexibleLicensingEnabled(plan) + await SubscriptionGroupHandler.promises.ensureAddSeatsEnabled(plan) res.render('subscriptions/add-seats', { subscriptionId: subscription._id, diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js index f6baea3852..e12fb09762 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js @@ -7,6 +7,8 @@ const SessionManager = require('../Authentication/SessionManager') const RecurlyClient = require('./RecurlyClient') const PlansLocator = require('./PlansLocator') const SubscriptionHandler = require('./SubscriptionHandler') +const GroupPlansData = require('./GroupPlansData') +const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./RecurlyEntities') async function removeUserFromGroup(subscriptionId, userIdToRemove) { await SubscriptionUpdater.promises.removeUserFromGroup( @@ -57,7 +59,13 @@ async function _replaceInArray(model, property, oldValue, newValue) { async function ensureFlexibleLicensingEnabled(plan) { if (!plan?.canUseFlexibleLicensing) { - throw new Error('The group plan does not support flexible licencing') + throw new Error('The group plan does not support flexible licensing') + } +} + +async function ensureAddSeatsEnabled(plan) { + if (!plan?.membersLimitAddOn) { + throw new Error('The group plan does not support adding seats') } } @@ -88,29 +96,60 @@ async function _addSeatsSubscriptionChange(req) { const { recurlySubscription, plan } = await getUsersGroupSubscriptionDetails(req) await ensureFlexibleLicensingEnabled(plan) + await ensureAddSeatsEnabled(plan) const userId = SessionManager.getLoggedInUserId(req.session) const currentAddonQuantity = recurlySubscription.addOns.find( - addOn => addOn.code === plan.membersLimitAddOn + addOn => addOn.code === MEMBERS_LIMIT_ADD_ON_CODE )?.quantity ?? 0 // Keeps only the new total quantity of addon const nextAddonQuantity = currentAddonQuantity + adding - const changeRequest = recurlySubscription.getRequestForAddOnUpdate( - plan.membersLimitAddOn, - nextAddonQuantity - ) + + let changeRequest + if (recurlySubscription.hasAddOn(MEMBERS_LIMIT_ADD_ON_CODE)) { + // Not providing a custom price as once the subscription is locked + // to an add-on at a given price, it will use it for subsequent payments + changeRequest = recurlySubscription.getRequestForAddOnUpdate( + MEMBERS_LIMIT_ADD_ON_CODE, + nextAddonQuantity + ) + } else { + let unitPrice + const newPlanPricesAppliedAt = new Date('2025-01-08T14:00:00Z') + const isLegacyPriceApplicable = + new Date(recurlySubscription.createdAt) < newPlanPricesAppliedAt + + if (isLegacyPriceApplicable) { + const pattern = + /^group_(collaborator|professional)_(5|10|20|50)_(educational|enterprise)$/ + const [, planCode, size, usage] = plan.planCode.match(pattern) + const currency = recurlySubscription.currency + const legacyPriceInCents = + GroupPlansData[usage][planCode][currency][size] + .additional_license_legacy_price_in_cents + + if (legacyPriceInCents > 0) { + unitPrice = legacyPriceInCents / 100 + } + } + + changeRequest = recurlySubscription.getRequestForAddOnPurchase( + MEMBERS_LIMIT_ADD_ON_CODE, + nextAddonQuantity, + unitPrice + ) + } return { changeRequest, userId, currentAddonQuantity, recurlySubscription, - plan, } } async function previewAddSeatsSubscriptionChange(req) { - const { changeRequest, userId, currentAddonQuantity, plan } = + const { changeRequest, userId, currentAddonQuantity } = await _addSeatsSubscriptionChange(req) const paymentMethod = await RecurlyClient.promises.getPaymentMethod(userId) const subscriptionChange = @@ -120,9 +159,9 @@ async function previewAddSeatsSubscriptionChange(req) { { type: 'add-on-update', addOn: { - code: plan.membersLimitAddOn, + code: MEMBERS_LIMIT_ADD_ON_CODE, quantity: subscriptionChange.nextAddOns.find( - addon => addon.code === plan.membersLimitAddOn + addon => addon.code === MEMBERS_LIMIT_ADD_ON_CODE ).quantity, prevQuantity: currentAddonQuantity, }, @@ -216,6 +255,7 @@ module.exports = { removeUserFromGroup: callbackify(removeUserFromGroup), replaceUserReferencesInGroups: callbackify(replaceUserReferencesInGroups), ensureFlexibleLicensingEnabled: callbackify(ensureFlexibleLicensingEnabled), + ensureAddSeatsEnabled: callbackify(ensureAddSeatsEnabled), getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup), isUserPartOfGroup: callbackify(isUserPartOfGroup), getGroupPlanUpgradePreview: callbackify(getGroupPlanUpgradePreview), @@ -224,6 +264,7 @@ module.exports = { removeUserFromGroup, replaceUserReferencesInGroups, ensureFlexibleLicensingEnabled, + ensureAddSeatsEnabled, getTotalConfirmedUsersInGroup, isUserPartOfGroup, getUsersGroupSubscriptionDetails, diff --git a/services/web/app/templates/plans/groups.json b/services/web/app/templates/plans/groups.json index b5817cc684..d6d9e3747b 100644 --- a/services/web/app/templates/plans/groups.json +++ b/services/web/app/templates/plans/groups.json @@ -12,16 +12,20 @@ "price_in_cents": 110000 }, "5": { - "price_in_cents": 137500 + "price_in_cents": 137500, + "additional_license_legacy_price_in_cents": 32100 }, "10": { - "price_in_cents": 275000 + "price_in_cents": 275000, + "additional_license_legacy_price_in_cents": 17900 }, "20": { - "price_in_cents": 550000 + "price_in_cents": 550000, + "additional_license_legacy_price_in_cents": 16500 }, "50": { - "price_in_cents": 1375000 + "price_in_cents": 1375000, + "additional_license_legacy_price_in_cents": 15100 } }, "BRL": { @@ -35,16 +39,20 @@ "price_in_cents": 239600 }, "5": { - "price_in_cents": 299500 + "price_in_cents": 299500, + "additional_license_legacy_price_in_cents": 69900 }, "10": { - "price_in_cents": 599000 + "price_in_cents": 599000, + "additional_license_legacy_price_in_cents": 38900 }, "20": { - "price_in_cents": 1198000 + "price_in_cents": 1198000, + "additional_license_legacy_price_in_cents": 35900 }, "50": { - "price_in_cents": 2995000 + "price_in_cents": 2995000, + "additional_license_legacy_price_in_cents": 32900 } }, "CAD": { @@ -58,16 +66,20 @@ "price_in_cents": 107600 }, "5": { - "price_in_cents": 134500 + "price_in_cents": 134500, + "additional_license_legacy_price_in_cents": 31400 }, "10": { - "price_in_cents": 269000 + "price_in_cents": 269000, + "additional_license_legacy_price_in_cents": 17500 }, "20": { - "price_in_cents": 538000 + "price_in_cents": 538000, + "additional_license_legacy_price_in_cents": 16100 }, "50": { - "price_in_cents": 1345000 + "price_in_cents": 1345000, + "additional_license_legacy_price_in_cents": 14800 } }, "CHF": { @@ -81,16 +93,20 @@ "price_in_cents": 95600 }, "5": { - "price_in_cents": 119500 + "price_in_cents": 119500, + "additional_license_legacy_price_in_cents": 27900 }, "10": { - "price_in_cents": 239000 + "price_in_cents": 239000, + "additional_license_legacy_price_in_cents": 15500 }, "20": { - "price_in_cents": 478000 + "price_in_cents": 478000, + "additional_license_legacy_price_in_cents": 14300 }, "50": { - "price_in_cents": 1195000 + "price_in_cents": 1195000, + "additional_license_legacy_price_in_cents": 13100 } }, "CLP": { @@ -104,16 +120,20 @@ "price_in_cents": 57837600 }, "5": { - "price_in_cents": 72297000 + "price_in_cents": 72297000, + "additional_license_legacy_price_in_cents": 16869300 }, "10": { - "price_in_cents": 144594000 + "price_in_cents": 144594000, + "additional_license_legacy_price_in_cents": 9398600 }, "20": { - "price_in_cents": 289188000 + "price_in_cents": 289188000, + "additional_license_legacy_price_in_cents": 8675600 }, "50": { - "price_in_cents": 722970000 + "price_in_cents": 722970000, + "additional_license_legacy_price_in_cents": 7952600 } }, "COP": { @@ -127,16 +147,20 @@ "price_in_cents": 189576000 }, "5": { - "price_in_cents": 236970000 + "price_in_cents": 236970000, + "additional_license_legacy_price_in_cents": 55293000 }, "10": { - "price_in_cents": 473940000 + "price_in_cents": 473940000, + "additional_license_legacy_price_in_cents": 30806100 }, "20": { - "price_in_cents": 947880000 + "price_in_cents": 947880000, + "additional_license_legacy_price_in_cents": 28436400 }, "50": { - "price_in_cents": 2000000000 + "price_in_cents": 2000000000, + "additional_license_legacy_price_in_cents": 26066700 } }, "DKK": { @@ -150,16 +174,20 @@ "price_in_cents": 570800 }, "5": { - "price_in_cents": 713500 + "price_in_cents": 713500, + "additional_license_legacy_price_in_cents": 166500 }, "10": { - "price_in_cents": 1427000 + "price_in_cents": 1427000, + "additional_license_legacy_price_in_cents": 92700 }, "20": { - "price_in_cents": 2854000 + "price_in_cents": 2854000, + "additional_license_legacy_price_in_cents": 85600 }, "50": { - "price_in_cents": 7135000 + "price_in_cents": 7135000, + "additional_license_legacy_price_in_cents": 78500 } }, "EUR": { @@ -173,16 +201,20 @@ "price_in_cents": 88400 }, "5": { - "price_in_cents": 110500 + "price_in_cents": 110500, + "additional_license_legacy_price_in_cents": 25800 }, "10": { - "price_in_cents": 221000 + "price_in_cents": 221000, + "additional_license_legacy_price_in_cents": 14300 }, "20": { - "price_in_cents": 442000 + "price_in_cents": 442000, + "additional_license_legacy_price_in_cents": 13200 }, "50": { - "price_in_cents": 1105000 + "price_in_cents": 1105000, + "additional_license_legacy_price_in_cents": 12100 } }, "GBP": { @@ -196,16 +228,20 @@ "price_in_cents": 76400 }, "5": { - "price_in_cents": 95500 + "price_in_cents": 95500, + "additional_license_legacy_price_in_cents": 22300 }, "10": { - "price_in_cents": 191000 + "price_in_cents": 191000, + "additional_license_legacy_price_in_cents": 12400 }, "20": { - "price_in_cents": 382000 + "price_in_cents": 382000, + "additional_license_legacy_price_in_cents": 11400 }, "50": { - "price_in_cents": 955000 + "price_in_cents": 955000, + "additional_license_legacy_price_in_cents": 10500 } }, "INR": { @@ -219,16 +255,20 @@ "price_in_cents": 2303600 }, "5": { - "price_in_cents": 2879500 + "price_in_cents": 2879500, + "additional_license_legacy_price_in_cents": 671900 }, "10": { - "price_in_cents": 5759000 + "price_in_cents": 5759000, + "additional_license_legacy_price_in_cents": 374300 }, "20": { - "price_in_cents": 11518000 + "price_in_cents": 11518000, + "additional_license_legacy_price_in_cents": 345500 }, "50": { - "price_in_cents": 28795000 + "price_in_cents": 28795000, + "additional_license_legacy_price_in_cents": 316700 } }, "MXN": { @@ -242,16 +282,20 @@ "price_in_cents": 1415600 }, "5": { - "price_in_cents": 1769500 + "price_in_cents": 1769500, + "additional_license_legacy_price_in_cents": 412900 }, "10": { - "price_in_cents": 3539000 + "price_in_cents": 3539000, + "additional_license_legacy_price_in_cents": 230000 }, "20": { - "price_in_cents": 7078000 + "price_in_cents": 7078000, + "additional_license_legacy_price_in_cents": 212300 }, "50": { - "price_in_cents": 17695000 + "price_in_cents": 17695000, + "additional_license_legacy_price_in_cents": 194600 } }, "NOK": { @@ -265,16 +309,20 @@ "price_in_cents": 688400 }, "5": { - "price_in_cents": 860500 + "price_in_cents": 860500, + "additional_license_legacy_price_in_cents": 200800 }, "10": { - "price_in_cents": 1721000 + "price_in_cents": 1721000, + "additional_license_legacy_price_in_cents": 111800 }, "20": { - "price_in_cents": 3442000 + "price_in_cents": 3442000, + "additional_license_legacy_price_in_cents": 103200 }, "50": { - "price_in_cents": 8605000 + "price_in_cents": 8605000, + "additional_license_legacy_price_in_cents": 94600 } }, "NZD": { @@ -288,16 +336,20 @@ "price_in_cents": 110000 }, "5": { - "price_in_cents": 137500 + "price_in_cents": 137500, + "additional_license_legacy_price_in_cents": 32100 }, "10": { - "price_in_cents": 275000 + "price_in_cents": 275000, + "additional_license_legacy_price_in_cents": 17900 }, "20": { - "price_in_cents": 550000 + "price_in_cents": 550000, + "additional_license_legacy_price_in_cents": 16500 }, "50": { - "price_in_cents": 1375000 + "price_in_cents": 1375000, + "additional_license_legacy_price_in_cents": 15100 } }, "PEN": { @@ -311,16 +363,20 @@ "price_in_cents": 230000 }, "5": { - "price_in_cents": 287500 + "price_in_cents": 287500, + "additional_license_legacy_price_in_cents": 67100 }, "10": { - "price_in_cents": 575000 + "price_in_cents": 575000, + "additional_license_legacy_price_in_cents": 37400 }, "20": { - "price_in_cents": 1150000 + "price_in_cents": 1150000, + "additional_license_legacy_price_in_cents": 34500 }, "50": { - "price_in_cents": 2875000 + "price_in_cents": 2875000, + "additional_license_legacy_price_in_cents": 31600 } }, "SEK": { @@ -334,16 +390,20 @@ "price_in_cents": 688400 }, "5": { - "price_in_cents": 860500 + "price_in_cents": 860500, + "additional_license_legacy_price_in_cents": 200800 }, "10": { - "price_in_cents": 1721000 + "price_in_cents": 1721000, + "additional_license_legacy_price_in_cents": 111800 }, "20": { - "price_in_cents": 3442000 + "price_in_cents": 3442000, + "additional_license_legacy_price_in_cents": 103200 }, "50": { - "price_in_cents": 8605000 + "price_in_cents": 8605000, + "additional_license_legacy_price_in_cents": 94600 } }, "SGD": { @@ -357,16 +417,20 @@ "price_in_cents": 124400 }, "5": { - "price_in_cents": 155500 + "price_in_cents": 155500, + "additional_license_legacy_price_in_cents": 36300 }, "10": { - "price_in_cents": 311000 + "price_in_cents": 311000, + "additional_license_legacy_price_in_cents": 20200 }, "20": { - "price_in_cents": 622000 + "price_in_cents": 622000, + "additional_license_legacy_price_in_cents": 18600 }, "50": { - "price_in_cents": 1555000 + "price_in_cents": 1555000, + "additional_license_legacy_price_in_cents": 17100 } }, "USD": { @@ -380,16 +444,20 @@ "price_in_cents": 95600 }, "5": { - "price_in_cents": 119500 + "price_in_cents": 119500, + "additional_license_legacy_price_in_cents": 27900 }, "10": { - "price_in_cents": 239000 + "price_in_cents": 239000, + "additional_license_legacy_price_in_cents": 15500 }, "20": { - "price_in_cents": 478000 + "price_in_cents": 478000, + "additional_license_legacy_price_in_cents": 14300 }, "50": { - "price_in_cents": 1195000 + "price_in_cents": 1195000, + "additional_license_legacy_price_in_cents": 13100 } } }, @@ -405,16 +473,20 @@ "price_in_cents": 57200 }, "5": { - "price_in_cents": 71500 + "price_in_cents": 71500, + "additional_license_legacy_price_in_cents": 16700 }, "10": { - "price_in_cents": 143000 + "price_in_cents": 143000, + "additional_license_legacy_price_in_cents": 9300 }, "20": { - "price_in_cents": 286000 + "price_in_cents": 286000, + "additional_license_legacy_price_in_cents": 8600 }, "50": { - "price_in_cents": 715000 + "price_in_cents": 715000, + "additional_license_legacy_price_in_cents": 7800 } }, "BRL": { @@ -428,16 +500,20 @@ "price_in_cents": 119600 }, "5": { - "price_in_cents": 149500 + "price_in_cents": 149500, + "additional_license_legacy_price_in_cents": 34900 }, "10": { - "price_in_cents": 299000 + "price_in_cents": 299000, + "additional_license_legacy_price_in_cents": 19400 }, "20": { - "price_in_cents": 598000 + "price_in_cents": 598000, + "additional_license_legacy_price_in_cents": 17900 }, "50": { - "price_in_cents": 1495000 + "price_in_cents": 1495000, + "additional_license_legacy_price_in_cents": 16400 } }, "CAD": { @@ -451,16 +527,20 @@ "price_in_cents": 54800 }, "5": { - "price_in_cents": 68500 + "price_in_cents": 68500, + "additional_license_legacy_price_in_cents": 16000 }, "10": { - "price_in_cents": 137000 + "price_in_cents": 137000, + "additional_license_legacy_price_in_cents": 8900 }, "20": { - "price_in_cents": 274000 + "price_in_cents": 274000, + "additional_license_legacy_price_in_cents": 8200 }, "50": { - "price_in_cents": 685000 + "price_in_cents": 685000, + "additional_license_legacy_price_in_cents": 7500 } }, "CHF": { @@ -474,16 +554,20 @@ "price_in_cents": 47600 }, "5": { - "price_in_cents": 59500 + "price_in_cents": 59500, + "additional_license_legacy_price_in_cents": 13900 }, "10": { - "price_in_cents": 119000 + "price_in_cents": 119000, + "additional_license_legacy_price_in_cents": 7700 }, "20": { - "price_in_cents": 238000 + "price_in_cents": 238000, + "additional_license_legacy_price_in_cents": 7100 }, "50": { - "price_in_cents": 595000 + "price_in_cents": 595000, + "additional_license_legacy_price_in_cents": 6500 } }, "CLP": { @@ -497,16 +581,20 @@ "price_in_cents": 26637600 }, "5": { - "price_in_cents": 33297000 + "price_in_cents": 33297000, + "additional_license_legacy_price_in_cents": 7769300 }, "10": { - "price_in_cents": 66594000 + "price_in_cents": 66594000, + "additional_license_legacy_price_in_cents": 4328600 }, "20": { - "price_in_cents": 133188000 + "price_in_cents": 133188000, + "additional_license_legacy_price_in_cents": 3995600 }, "50": { - "price_in_cents": 332970000 + "price_in_cents": 332970000, + "additional_license_legacy_price_in_cents": 3662600 } }, "COP": { @@ -520,16 +608,20 @@ "price_in_cents": 93576000 }, "5": { - "price_in_cents": 116970000 + "price_in_cents": 116970000, + "additional_license_legacy_price_in_cents": 27293000 }, "10": { - "price_in_cents": 233940000 + "price_in_cents": 233940000, + "additional_license_legacy_price_in_cents": 15206100 }, "20": { - "price_in_cents": 467880000 + "price_in_cents": 467880000, + "additional_license_legacy_price_in_cents": 14036400 }, "50": { - "price_in_cents": 1169700000 + "price_in_cents": 1169700000, + "additional_license_legacy_price_in_cents": 12866700 } }, "DKK": { @@ -543,16 +635,20 @@ "price_in_cents": 287600 }, "5": { - "price_in_cents": 359500 + "price_in_cents": 359500, + "additional_license_legacy_price_in_cents": 83900 }, "10": { - "price_in_cents": 719000 + "price_in_cents": 719000, + "additional_license_legacy_price_in_cents": 46700 }, "20": { - "price_in_cents": 1438000 + "price_in_cents": 1438000, + "additional_license_legacy_price_in_cents": 43100 }, "50": { - "price_in_cents": 3595000 + "price_in_cents": 3595000, + "additional_license_legacy_price_in_cents": 39500 } }, "EUR": { @@ -566,16 +662,20 @@ "price_in_cents": 42800 }, "5": { - "price_in_cents": 53500 + "price_in_cents": 53500, + "additional_license_legacy_price_in_cents": 12500 }, "10": { - "price_in_cents": 107000 + "price_in_cents": 107000, + "additional_license_legacy_price_in_cents": 6900 }, "20": { - "price_in_cents": 214000 + "price_in_cents": 214000, + "additional_license_legacy_price_in_cents": 6400 }, "50": { - "price_in_cents": 535000 + "price_in_cents": 535000, + "additional_license_legacy_price_in_cents": 5900 } }, "GBP": { @@ -589,16 +689,20 @@ "price_in_cents": 38000 }, "5": { - "price_in_cents": 47500 + "price_in_cents": 47500, + "additional_license_legacy_price_in_cents": 11100 }, "10": { - "price_in_cents": 95000 + "price_in_cents": 95000, + "additional_license_legacy_price_in_cents": 6200 }, "20": { - "price_in_cents": 190000 + "price_in_cents": 190000, + "additional_license_legacy_price_in_cents": 5700 }, "50": { - "price_in_cents": 475000 + "price_in_cents": 475000, + "additional_license_legacy_price_in_cents": 5200 } }, "INR": { @@ -612,16 +716,20 @@ "price_in_cents": 1103600 }, "5": { - "price_in_cents": 1379500 + "price_in_cents": 1379500, + "additional_license_legacy_price_in_cents": 321900 }, "10": { - "price_in_cents": 2759000 + "price_in_cents": 2759000, + "additional_license_legacy_price_in_cents": 179300 }, "20": { - "price_in_cents": 5518000 + "price_in_cents": 5518000, + "additional_license_legacy_price_in_cents": 165500 }, "50": { - "price_in_cents": 13795000 + "price_in_cents": 13795000, + "additional_license_legacy_price_in_cents": 151700 } }, "MXN": { @@ -635,16 +743,20 @@ "price_in_cents": 695600 }, "5": { - "price_in_cents": 869500 + "price_in_cents": 869500, + "additional_license_legacy_price_in_cents": 202900 }, "10": { - "price_in_cents": 1739000 + "price_in_cents": 1739000, + "additional_license_legacy_price_in_cents": 113000 }, "20": { - "price_in_cents": 3478000 + "price_in_cents": 3478000, + "additional_license_legacy_price_in_cents": 104300 }, "50": { - "price_in_cents": 8695000 + "price_in_cents": 8695000, + "additional_license_legacy_price_in_cents": 95600 } }, "NOK": { @@ -658,16 +770,20 @@ "price_in_cents": 347600 }, "5": { - "price_in_cents": 434500 + "price_in_cents": 434500, + "additional_license_legacy_price_in_cents": 101400 }, "10": { - "price_in_cents": 869000 + "price_in_cents": 869000, + "additional_license_legacy_price_in_cents": 56500 }, "20": { - "price_in_cents": 1738000 + "price_in_cents": 1738000, + "additional_license_legacy_price_in_cents": 52100 }, "50": { - "price_in_cents": 4345000 + "price_in_cents": 4345000, + "additional_license_legacy_price_in_cents": 47800 } }, "NZD": { @@ -681,16 +797,20 @@ "price_in_cents": 57200 }, "5": { - "price_in_cents": 71500 + "price_in_cents": 71500, + "additional_license_legacy_price_in_cents": 16700 }, "10": { - "price_in_cents": 143000 + "price_in_cents": 143000, + "additional_license_legacy_price_in_cents": 9300 }, "20": { - "price_in_cents": 286000 + "price_in_cents": 286000, + "additional_license_legacy_price_in_cents": 8600 }, "50": { - "price_in_cents": 715000 + "price_in_cents": 715000, + "additional_license_legacy_price_in_cents": 7800 } }, "PEN": { @@ -704,16 +824,20 @@ "price_in_cents": 110000 }, "5": { - "price_in_cents": 137500 + "price_in_cents": 137500, + "additional_license_legacy_price_in_cents": 32100 }, "10": { - "price_in_cents": 275000 + "price_in_cents": 275000, + "additional_license_legacy_price_in_cents": 17900 }, "20": { - "price_in_cents": 550000 + "price_in_cents": 550000, + "additional_license_legacy_price_in_cents": 16500 }, "50": { - "price_in_cents": 1375000 + "price_in_cents": 1375000, + "additional_license_legacy_price_in_cents": 15100 } }, "SEK": { @@ -727,16 +851,20 @@ "price_in_cents": 347600 }, "5": { - "price_in_cents": 434500 + "price_in_cents": 434500, + "additional_license_legacy_price_in_cents": 101400 }, "10": { - "price_in_cents": 869000 + "price_in_cents": 869000, + "additional_license_legacy_price_in_cents": 56500 }, "20": { - "price_in_cents": 1738000 + "price_in_cents": 1738000, + "additional_license_legacy_price_in_cents": 52100 }, "50": { - "price_in_cents": 4345000 + "price_in_cents": 4345000, + "additional_license_legacy_price_in_cents": 47800 } }, "SGD": { @@ -750,16 +878,20 @@ "price_in_cents": 62000 }, "5": { - "price_in_cents": 77500 + "price_in_cents": 77500, + "additional_license_legacy_price_in_cents": 18100 }, "10": { - "price_in_cents": 155000 + "price_in_cents": 155000, + "additional_license_legacy_price_in_cents": 10100 }, "20": { - "price_in_cents": 310000 + "price_in_cents": 310000, + "additional_license_legacy_price_in_cents": 9300 }, "50": { - "price_in_cents": 775000 + "price_in_cents": 775000, + "additional_license_legacy_price_in_cents": 8500 } }, "USD": { @@ -773,16 +905,20 @@ "price_in_cents": 47600 }, "5": { - "price_in_cents": 59500 + "price_in_cents": 59500, + "additional_license_legacy_price_in_cents": 13900 }, "10": { - "price_in_cents": 119000 + "price_in_cents": 119000, + "additional_license_legacy_price_in_cents": 7700 }, "20": { - "price_in_cents": 238000 + "price_in_cents": 238000, + "additional_license_legacy_price_in_cents": 7100 }, "50": { - "price_in_cents": 595000 + "price_in_cents": 595000, + "additional_license_legacy_price_in_cents": 6500 } } } @@ -800,16 +936,20 @@ "price_in_cents": 183600 }, "5": { - "price_in_cents": 229500 + "price_in_cents": 229500, + "additional_license_legacy_price_in_cents": 32100 }, "10": { - "price_in_cents": 459000 + "price_in_cents": 459000, + "additional_license_legacy_price_in_cents": 29800 }, "20": { - "price_in_cents": 918000 + "price_in_cents": 918000, + "additional_license_legacy_price_in_cents": 27500 }, "50": { - "price_in_cents": 2295000 + "price_in_cents": 2295000, + "additional_license_legacy_price_in_cents": 25200 } }, "BRL": { @@ -823,16 +963,20 @@ "price_in_cents": 399600 }, "5": { - "price_in_cents": 499500 + "price_in_cents": 499500, + "additional_license_legacy_price_in_cents": 69900 }, "10": { - "price_in_cents": 999000 + "price_in_cents": 999000, + "additional_license_legacy_price_in_cents": 64900 }, "20": { - "price_in_cents": 1998000 + "price_in_cents": 1998000, + "additional_license_legacy_price_in_cents": 59900 }, "50": { - "price_in_cents": 4995000 + "price_in_cents": 4995000, + "additional_license_legacy_price_in_cents": 54900 } }, "CAD": { @@ -846,16 +990,20 @@ "price_in_cents": 179600 }, "5": { - "price_in_cents": 224500 + "price_in_cents": 224500, + "additional_license_legacy_price_in_cents": 31400 }, "10": { - "price_in_cents": 449000 + "price_in_cents": 449000, + "additional_license_legacy_price_in_cents": 29100 }, "20": { - "price_in_cents": 898000 + "price_in_cents": 898000, + "additional_license_legacy_price_in_cents": 26900 }, "50": { - "price_in_cents": 2245000 + "price_in_cents": 2245000, + "additional_license_legacy_price_in_cents": 24600 } }, "CHF": { @@ -869,16 +1017,20 @@ "price_in_cents": 159600 }, "5": { - "price_in_cents": 199500 + "price_in_cents": 199500, + "additional_license_legacy_price_in_cents": 49900 }, "10": { - "price_in_cents": 399000 + "price_in_cents": 399000, + "additional_license_legacy_price_in_cents": 25900 }, "20": { - "price_in_cents": 798000 + "price_in_cents": 798000, + "additional_license_legacy_price_in_cents": 23900 }, "50": { - "price_in_cents": 1995000 + "price_in_cents": 1995000, + "additional_license_legacy_price_in_cents": 21900 } }, "CLP": { @@ -892,16 +1044,20 @@ "price_in_cents": 96396000 }, "5": { - "price_in_cents": 120495000 + "price_in_cents": 120495000, + "additional_license_legacy_price_in_cents": 16869300 }, "10": { - "price_in_cents": 240990000 + "price_in_cents": 240990000, + "additional_license_legacy_price_in_cents": 15664300 }, "20": { - "price_in_cents": 481980000 + "price_in_cents": 481980000, + "additional_license_legacy_price_in_cents": 14459400 }, "50": { - "price_in_cents": 1204950000 + "price_in_cents": 1204950000, + "additional_license_legacy_price_in_cents": 13254400 } }, "COP": { @@ -915,16 +1071,20 @@ "price_in_cents": 315960000 }, "5": { - "price_in_cents": 394950000 + "price_in_cents": 394950000, + "additional_license_legacy_price_in_cents": 55293000 }, "10": { - "price_in_cents": 789900000 + "price_in_cents": 789900000, + "additional_license_legacy_price_in_cents": 51343500 }, "20": { - "price_in_cents": 1579800000 + "price_in_cents": 1579800000, + "additional_license_legacy_price_in_cents": 47394000 }, "50": { - "price_in_cents": 2000000000 + "price_in_cents": 2000000000, + "additional_license_legacy_price_in_cents": 40000000 } }, "DKK": { @@ -938,16 +1098,20 @@ "price_in_cents": 951600 }, "5": { - "price_in_cents": 1189500 + "price_in_cents": 1189500, + "additional_license_legacy_price_in_cents": 166500 }, "10": { - "price_in_cents": 2379000 + "price_in_cents": 2379000, + "additional_license_legacy_price_in_cents": 154600 }, "20": { - "price_in_cents": 4758000 + "price_in_cents": 4758000, + "additional_license_legacy_price_in_cents": 142700 }, "50": { - "price_in_cents": 11895000 + "price_in_cents": 11895000, + "additional_license_legacy_price_in_cents": 130800 } }, "EUR": { @@ -961,16 +1125,20 @@ "price_in_cents": 147600 }, "5": { - "price_in_cents": 184500 + "price_in_cents": 184500, + "additional_license_legacy_price_in_cents": 25800 }, "10": { - "price_in_cents": 369000 + "price_in_cents": 369000, + "additional_license_legacy_price_in_cents": 23900 }, "20": { - "price_in_cents": 738000 + "price_in_cents": 738000, + "additional_license_legacy_price_in_cents": 22100 }, "50": { - "price_in_cents": 1845000 + "price_in_cents": 1845000, + "additional_license_legacy_price_in_cents": 20200 } }, "GBP": { @@ -984,16 +1152,20 @@ "price_in_cents": 127600 }, "5": { - "price_in_cents": 159500 + "price_in_cents": 159500, + "additional_license_legacy_price_in_cents": 22300 }, "10": { - "price_in_cents": 319000 + "price_in_cents": 319000, + "additional_license_legacy_price_in_cents": 20700 }, "20": { - "price_in_cents": 638000 + "price_in_cents": 638000, + "additional_license_legacy_price_in_cents": 19100 }, "50": { - "price_in_cents": 1595000 + "price_in_cents": 1595000, + "additional_license_legacy_price_in_cents": 17500 } }, "INR": { @@ -1007,16 +1179,20 @@ "price_in_cents": 3839600 }, "5": { - "price_in_cents": 4799500 + "price_in_cents": 4799500, + "additional_license_legacy_price_in_cents": 671900 }, "10": { - "price_in_cents": 9599000 + "price_in_cents": 9599000, + "additional_license_legacy_price_in_cents": 623900 }, "20": { - "price_in_cents": 19198000 + "price_in_cents": 19198000, + "additional_license_legacy_price_in_cents": 575900 }, "50": { - "price_in_cents": 47995000 + "price_in_cents": 47995000, + "additional_license_legacy_price_in_cents": 527900 } }, "MXN": { @@ -1030,16 +1206,20 @@ "price_in_cents": 2359600 }, "5": { - "price_in_cents": 2949500 + "price_in_cents": 2949500, + "additional_license_legacy_price_in_cents": 412900 }, "10": { - "price_in_cents": 5899000 + "price_in_cents": 5899000, + "additional_license_legacy_price_in_cents": 383400 }, "20": { - "price_in_cents": 11798000 + "price_in_cents": 11798000, + "additional_license_legacy_price_in_cents": 353900 }, "50": { - "price_in_cents": 29495000 + "price_in_cents": 29495000, + "additional_license_legacy_price_in_cents": 324400 } }, "NOK": { @@ -1053,16 +1233,20 @@ "price_in_cents": 1147600 }, "5": { - "price_in_cents": 1434500 + "price_in_cents": 1434500, + "additional_license_legacy_price_in_cents": 200800 }, "10": { - "price_in_cents": 2869000 + "price_in_cents": 2869000, + "additional_license_legacy_price_in_cents": 186400 }, "20": { - "price_in_cents": 5738000 + "price_in_cents": 5738000, + "additional_license_legacy_price_in_cents": 172100 }, "50": { - "price_in_cents": 14345000 + "price_in_cents": 14345000, + "additional_license_legacy_price_in_cents": 157700 } }, "NZD": { @@ -1076,16 +1260,20 @@ "price_in_cents": 183600 }, "5": { - "price_in_cents": 229500 + "price_in_cents": 229500, + "additional_license_legacy_price_in_cents": 32100 }, "10": { - "price_in_cents": 459000 + "price_in_cents": 459000, + "additional_license_legacy_price_in_cents": 29800 }, "20": { - "price_in_cents": 918000 + "price_in_cents": 918000, + "additional_license_legacy_price_in_cents": 27500 }, "50": { - "price_in_cents": 2295000 + "price_in_cents": 2295000, + "additional_license_legacy_price_in_cents": 25200 } }, "PEN": { @@ -1099,16 +1287,20 @@ "price_in_cents": 383600 }, "5": { - "price_in_cents": 479500 + "price_in_cents": 479500, + "additional_license_legacy_price_in_cents": 67100 }, "10": { - "price_in_cents": 959000 + "price_in_cents": 959000, + "additional_license_legacy_price_in_cents": 62300 }, "20": { - "price_in_cents": 1918000 + "price_in_cents": 1918000, + "additional_license_legacy_price_in_cents": 57500 }, "50": { - "price_in_cents": 4795000 + "price_in_cents": 4795000, + "additional_license_legacy_price_in_cents": 52700 } }, "SEK": { @@ -1122,16 +1314,20 @@ "price_in_cents": 1147600 }, "5": { - "price_in_cents": 1434500 + "price_in_cents": 1434500, + "additional_license_legacy_price_in_cents": 200800 }, "10": { - "price_in_cents": 2869000 + "price_in_cents": 2869000, + "additional_license_legacy_price_in_cents": 186400 }, "20": { - "price_in_cents": 5738000 + "price_in_cents": 5738000, + "additional_license_legacy_price_in_cents": 172100 }, "50": { - "price_in_cents": 14345000 + "price_in_cents": 14345000, + "additional_license_legacy_price_in_cents": 157700 } }, "SGD": { @@ -1145,16 +1341,20 @@ "price_in_cents": 207600 }, "5": { - "price_in_cents": 259500 + "price_in_cents": 259500, + "additional_license_legacy_price_in_cents": 36300 }, "10": { - "price_in_cents": 519000 + "price_in_cents": 519000, + "additional_license_legacy_price_in_cents": 33700 }, "20": { - "price_in_cents": 1038000 + "price_in_cents": 1038000, + "additional_license_legacy_price_in_cents": 31100 }, "50": { - "price_in_cents": 2595000 + "price_in_cents": 2595000, + "additional_license_legacy_price_in_cents": 28500 } }, "USD": { @@ -1168,16 +1368,20 @@ "price_in_cents": 159600 }, "5": { - "price_in_cents": 199500 + "price_in_cents": 199500, + "additional_license_legacy_price_in_cents": 27900 }, "10": { - "price_in_cents": 399000 + "price_in_cents": 399000, + "additional_license_legacy_price_in_cents": 25900 }, "20": { - "price_in_cents": 798000 + "price_in_cents": 798000, + "additional_license_legacy_price_in_cents": 23900 }, "50": { - "price_in_cents": 1995000 + "price_in_cents": 1995000, + "additional_license_legacy_price_in_cents": 21900 } } }, @@ -1193,16 +1397,20 @@ "price_in_cents": 95600 }, "5": { - "price_in_cents": 119500 + "price_in_cents": 119500, + "additional_license_legacy_price_in_cents": 16700 }, "10": { - "price_in_cents": 239000 + "price_in_cents": 239000, + "additional_license_legacy_price_in_cents": 15500 }, "20": { - "price_in_cents": 478000 + "price_in_cents": 478000, + "additional_license_legacy_price_in_cents": 14300 }, "50": { - "price_in_cents": 1195000 + "price_in_cents": 1195000, + "additional_license_legacy_price_in_cents": 13100 } }, "BRL": { @@ -1216,16 +1424,20 @@ "price_in_cents": 199600 }, "5": { - "price_in_cents": 249500 + "price_in_cents": 249500, + "additional_license_legacy_price_in_cents": 34900 }, "10": { - "price_in_cents": 499000 + "price_in_cents": 499000, + "additional_license_legacy_price_in_cents": 32400 }, "20": { - "price_in_cents": 998000 + "price_in_cents": 998000, + "additional_license_legacy_price_in_cents": 29900 }, "50": { - "price_in_cents": 2495000 + "price_in_cents": 2495000, + "additional_license_legacy_price_in_cents": 27400 } }, "CAD": { @@ -1239,16 +1451,20 @@ "price_in_cents": 91600 }, "5": { - "price_in_cents": 114500 + "price_in_cents": 114500, + "additional_license_legacy_price_in_cents": 16000 }, "10": { - "price_in_cents": 229000 + "price_in_cents": 229000, + "additional_license_legacy_price_in_cents": 14800 }, "20": { - "price_in_cents": 458000 + "price_in_cents": 458000, + "additional_license_legacy_price_in_cents": 13700 }, "50": { - "price_in_cents": 1145000 + "price_in_cents": 1145000, + "additional_license_legacy_price_in_cents": 12500 } }, "CHF": { @@ -1262,16 +1478,20 @@ "price_in_cents": 79600 }, "5": { - "price_in_cents": 99500 + "price_in_cents": 99500, + "additional_license_legacy_price_in_cents": 13900 }, "10": { - "price_in_cents": 199000 + "price_in_cents": 199000, + "additional_license_legacy_price_in_cents": 12900 }, "20": { - "price_in_cents": 398000 + "price_in_cents": 398000, + "additional_license_legacy_price_in_cents": 11900 }, "50": { - "price_in_cents": 995000 + "price_in_cents": 995000, + "additional_license_legacy_price_in_cents": 10900 } }, "CLP": { @@ -1285,16 +1505,20 @@ "price_in_cents": 44396000 }, "5": { - "price_in_cents": 55495000 + "price_in_cents": 55495000, + "additional_license_legacy_price_in_cents": 7769300 }, "10": { - "price_in_cents": 110990000 + "price_in_cents": 110990000, + "additional_license_legacy_price_in_cents": 7214300 }, "20": { - "price_in_cents": 221980000 + "price_in_cents": 221980000, + "additional_license_legacy_price_in_cents": 6659400 }, "50": { - "price_in_cents": 554950000 + "price_in_cents": 554950000, + "additional_license_legacy_price_in_cents": 6104400 } }, "COP": { @@ -1308,16 +1532,20 @@ "price_in_cents": 155960000 }, "5": { - "price_in_cents": 194950000 + "price_in_cents": 194950000, + "additional_license_legacy_price_in_cents": 27293000 }, "10": { - "price_in_cents": 389900000 + "price_in_cents": 389900000, + "additional_license_legacy_price_in_cents": 25343500 }, "20": { - "price_in_cents": 779800000 + "price_in_cents": 779800000, + "additional_license_legacy_price_in_cents": 23394000 }, "50": { - "price_in_cents": 1949500000 + "price_in_cents": 1949500000, + "additional_license_legacy_price_in_cents": 21444500 } }, "DKK": { @@ -1331,16 +1559,20 @@ "price_in_cents": 479600 }, "5": { - "price_in_cents": 599500 + "price_in_cents": 599500, + "additional_license_legacy_price_in_cents": 83900 }, "10": { - "price_in_cents": 1199000 + "price_in_cents": 1199000, + "additional_license_legacy_price_in_cents": 77900 }, "20": { - "price_in_cents": 2398000 + "price_in_cents": 2398000, + "additional_license_legacy_price_in_cents": 71900 }, "50": { - "price_in_cents": 5995000 + "price_in_cents": 5995000, + "additional_license_legacy_price_in_cents": 65900 } }, "EUR": { @@ -1354,16 +1586,20 @@ "price_in_cents": 71600 }, "5": { - "price_in_cents": 89500 + "price_in_cents": 89500, + "additional_license_legacy_price_in_cents": 12500 }, "10": { - "price_in_cents": 179000 + "price_in_cents": 179000, + "additional_license_legacy_price_in_cents": 11600 }, "20": { - "price_in_cents": 358000 + "price_in_cents": 358000, + "additional_license_legacy_price_in_cents": 10700 }, "50": { - "price_in_cents": 895000 + "price_in_cents": 895000, + "additional_license_legacy_price_in_cents": 9800 } }, "GBP": { @@ -1377,16 +1613,20 @@ "price_in_cents": 63600 }, "5": { - "price_in_cents": 79500 + "price_in_cents": 79500, + "additional_license_legacy_price_in_cents": 11100 }, "10": { - "price_in_cents": 159000 + "price_in_cents": 159000, + "additional_license_legacy_price_in_cents": 10300 }, "20": { - "price_in_cents": 318000 + "price_in_cents": 318000, + "additional_license_legacy_price_in_cents": 9500 }, "50": { - "price_in_cents": 795000 + "price_in_cents": 795000, + "additional_license_legacy_price_in_cents": 8700 } }, "INR": { @@ -1400,16 +1640,20 @@ "price_in_cents": 1839600 }, "5": { - "price_in_cents": 2299500 + "price_in_cents": 2299500, + "additional_license_legacy_price_in_cents": 321900 }, "10": { - "price_in_cents": 4599000 + "price_in_cents": 4599000, + "additional_license_legacy_price_in_cents": 298900 }, "20": { - "price_in_cents": 9198000 + "price_in_cents": 9198000, + "additional_license_legacy_price_in_cents": 275900 }, "50": { - "price_in_cents": 22995000 + "price_in_cents": 22995000, + "additional_license_legacy_price_in_cents": 252900 } }, "MXN": { @@ -1423,16 +1667,20 @@ "price_in_cents": 1159600 }, "5": { - "price_in_cents": 1449500 + "price_in_cents": 1449500, + "additional_license_legacy_price_in_cents": 202900 }, "10": { - "price_in_cents": 2899000 + "price_in_cents": 2899000, + "additional_license_legacy_price_in_cents": 188400 }, "20": { - "price_in_cents": 5798000 + "price_in_cents": 5798000, + "additional_license_legacy_price_in_cents": 173900 }, "50": { - "price_in_cents": 14495000 + "price_in_cents": 14495000, + "additional_license_legacy_price_in_cents": 159400 } }, "NOK": { @@ -1446,16 +1694,20 @@ "price_in_cents": 579600 }, "5": { - "price_in_cents": 724500 + "price_in_cents": 724500, + "additional_license_legacy_price_in_cents": 101400 }, "10": { - "price_in_cents": 1449000 + "price_in_cents": 1449000, + "additional_license_legacy_price_in_cents": 94100 }, "20": { - "price_in_cents": 2898000 + "price_in_cents": 2898000, + "additional_license_legacy_price_in_cents": 86900 }, "50": { - "price_in_cents": 7245000 + "price_in_cents": 7245000, + "additional_license_legacy_price_in_cents": 79600 } }, "NZD": { @@ -1469,16 +1721,20 @@ "price_in_cents": 95600 }, "5": { - "price_in_cents": 119500 + "price_in_cents": 119500, + "additional_license_legacy_price_in_cents": 16700 }, "10": { - "price_in_cents": 239000 + "price_in_cents": 239000, + "additional_license_legacy_price_in_cents": 15500 }, "20": { - "price_in_cents": 478000 + "price_in_cents": 478000, + "additional_license_legacy_price_in_cents": 14300 }, "50": { - "price_in_cents": 1195000 + "price_in_cents": 1195000, + "additional_license_legacy_price_in_cents": 13100 } }, "PEN": { @@ -1492,16 +1748,20 @@ "price_in_cents": 183600 }, "5": { - "price_in_cents": 229500 + "price_in_cents": 229500, + "additional_license_legacy_price_in_cents": 32100 }, "10": { - "price_in_cents": 459000 + "price_in_cents": 459000, + "additional_license_legacy_price_in_cents": 29800 }, "20": { - "price_in_cents": 918000 + "price_in_cents": 918000, + "additional_license_legacy_price_in_cents": 27500 }, "50": { - "price_in_cents": 2295000 + "price_in_cents": 2295000, + "additional_license_legacy_price_in_cents": 25200 } }, "SEK": { @@ -1515,16 +1775,20 @@ "price_in_cents": 579600 }, "5": { - "price_in_cents": 724500 + "price_in_cents": 724500, + "additional_license_legacy_price_in_cents": 101400 }, "10": { - "price_in_cents": 1449000 + "price_in_cents": 1449000, + "additional_license_legacy_price_in_cents": 94100 }, "20": { - "price_in_cents": 2898000 + "price_in_cents": 2898000, + "additional_license_legacy_price_in_cents": 86900 }, "50": { - "price_in_cents": 7245000 + "price_in_cents": 7245000, + "additional_license_legacy_price_in_cents": 79600 } }, "SGD": { @@ -1538,16 +1802,20 @@ "price_in_cents": 103600 }, "5": { - "price_in_cents": 129500 + "price_in_cents": 129500, + "additional_license_legacy_price_in_cents": 18100 }, "10": { - "price_in_cents": 259000 + "price_in_cents": 259000, + "additional_license_legacy_price_in_cents": 16800 }, "20": { - "price_in_cents": 518000 + "price_in_cents": 518000, + "additional_license_legacy_price_in_cents": 15500 }, "50": { - "price_in_cents": 1295000 + "price_in_cents": 1295000, + "additional_license_legacy_price_in_cents": 14200 } }, "USD": { @@ -1561,16 +1829,20 @@ "price_in_cents": 79600 }, "5": { - "price_in_cents": 99500 + "price_in_cents": 99500, + "additional_license_legacy_price_in_cents": 13900 }, "10": { - "price_in_cents": 199000 + "price_in_cents": 199000, + "additional_license_legacy_price_in_cents": 12900 }, "20": { - "price_in_cents": 398000 + "price_in_cents": 398000, + "additional_license_legacy_price_in_cents": 11900 }, "50": { - "price_in_cents": 995000 + "price_in_cents": 995000, + "additional_license_legacy_price_in_cents": 10900 } } } diff --git a/services/web/scripts/plan-prices/additional-license-add-on-legacy-prices.json b/services/web/scripts/plan-prices/additional-license-add-on-legacy-prices.json new file mode 100644 index 0000000000..8ab0f87355 --- /dev/null +++ b/services/web/scripts/plan-prices/additional-license-add-on-legacy-prices.json @@ -0,0 +1,318 @@ +{ + "educational": { + "professional": { + "5": { + "AUD": 321, + "BRL": 699, + "CAD": 314, + "CHF": 279, + "CLP": 168693, + "COP": 552930, + "DKK": 1665, + "EUR": 258, + "GBP": 223, + "INR": 6719, + "MXN": 4129, + "NOK": 2008, + "NZD": 321, + "PEN": 671, + "SEK": 2008, + "SGD": 363, + "USD": 279 + }, + "10": { + "AUD": 179, + "BRL": 389, + "CAD": 175, + "CHF": 155, + "CLP": 93986, + "COP": 308061, + "DKK": 927, + "EUR": 143, + "GBP": 124, + "INR": 3743, + "MXN": 2300, + "NOK": 1118, + "NZD": 179, + "PEN": 374, + "SEK": 1118, + "SGD": 202, + "USD": 155 + }, + "20": { + "AUD": 165, + "BRL": 359, + "CAD": 161, + "CHF": 143, + "CLP": 86756, + "COP": 284364, + "DKK": 856, + "EUR": 132, + "GBP": 114, + "INR": 3455, + "MXN": 2123, + "NOK": 1032, + "NZD": 165, + "PEN": 345, + "SEK": 1032, + "SGD": 186, + "USD": 143 + }, + "50": { + "AUD": 151, + "BRL": 329, + "CAD": 148, + "CHF": 131, + "CLP": 79526, + "COP": 260667, + "DKK": 785, + "EUR": 121, + "GBP": 105, + "INR": 3167, + "MXN": 1946, + "NOK": 946, + "NZD": 151, + "PEN": 316, + "SEK": 946, + "SGD": 171, + "USD": 131 + } + }, + "collaborator": { + "5": { + "AUD": 167, + "BRL": 349, + "CAD": 160, + "CHF": 139, + "CLP": 77693, + "COP": 272930, + "DKK": 839, + "EUR": 125, + "GBP": 111, + "INR": 3219, + "MXN": 2029, + "NOK": 1014, + "NZD": 167, + "PEN": 321, + "SEK": 1014, + "SGD": 181, + "USD": 139 + }, + "10": { + "AUD": 93, + "BRL": 194, + "CAD": 89, + "CHF": 77, + "CLP": 43286, + "COP": 152061, + "DKK": 467, + "EUR": 69, + "GBP": 62, + "INR": 1793, + "MXN": 1130, + "NOK": 565, + "NZD": 93, + "PEN": 179, + "SEK": 565, + "SGD": 101, + "USD": 77 + }, + "20": { + "AUD": 86, + "BRL": 179, + "CAD": 82, + "CHF": 71, + "CLP": 39956, + "COP": 140364, + "DKK": 431, + "EUR": 64, + "GBP": 57, + "INR": 1655, + "MXN": 1043, + "NOK": 521, + "NZD": 86, + "PEN": 165, + "SEK": 521, + "SGD": 93, + "USD": 71 + }, + "50": { + "AUD": 78, + "BRL": 164, + "CAD": 75, + "CHF": 65, + "CLP": 36626, + "COP": 128667, + "DKK": 395, + "EUR": 59, + "GBP": 52, + "INR": 1517, + "MXN": 956, + "NOK": 478, + "NZD": 78, + "PEN": 151, + "SEK": 478, + "SGD": 85, + "USD": 65 + } + } + }, + "enterprise": { + "professional": { + "5": { + "AUD": 321, + "BRL": 699, + "CAD": 314, + "CHF": 499, + "CLP": 168693, + "COP": 552930, + "DKK": 1665, + "EUR": 258, + "GBP": 223, + "INR": 6719, + "MXN": 4129, + "NOK": 2008, + "NZD": 321, + "PEN": 671, + "SEK": 2008, + "SGD": 363, + "USD": 279 + }, + "10": { + "AUD": 298, + "BRL": 649, + "CAD": 291, + "CHF": 259, + "CLP": 156643, + "COP": 513435, + "DKK": 1546, + "EUR": 239, + "GBP": 207, + "INR": 6239, + "MXN": 3834, + "NOK": 1864, + "NZD": 298, + "PEN": 623, + "SEK": 1864, + "SGD": 337, + "USD": 259 + }, + "20": { + "AUD": 275, + "BRL": 599, + "CAD": 269, + "CHF": 239, + "CLP": 144594, + "COP": 473940, + "DKK": 1427, + "EUR": 221, + "GBP": 191, + "INR": 5759, + "MXN": 3539, + "NOK": 1721, + "NZD": 275, + "PEN": 575, + "SEK": 1721, + "SGD": 311, + "USD": 239 + }, + "50": { + "AUD": 252, + "BRL": 549, + "CAD": 246, + "CHF": 219, + "CLP": 132544, + "COP": 400000, + "DKK": 1308, + "EUR": 202, + "GBP": 175, + "INR": 5279, + "MXN": 3244, + "NOK": 1577, + "NZD": 252, + "PEN": 527, + "SEK": 1577, + "SGD": 285, + "USD": 219 + } + }, + "collaborator": { + "5": { + "AUD": 167, + "BRL": 349, + "CAD": 160, + "CHF": 139, + "CLP": 77693, + "COP": 272930, + "DKK": 839, + "EUR": 125, + "GBP": 111, + "INR": 3219, + "MXN": 2029, + "NOK": 1014, + "NZD": 167, + "PEN": 321, + "SEK": 1014, + "SGD": 181, + "USD": 139 + }, + "10": { + "AUD": 155, + "BRL": 324, + "CAD": 148, + "CHF": 129, + "CLP": 72143, + "COP": 253435, + "DKK": 779, + "EUR": 116, + "GBP": 103, + "INR": 2989, + "MXN": 1884, + "NOK": 941, + "NZD": 155, + "PEN": 298, + "SEK": 941, + "SGD": 168, + "USD": 129 + }, + "20": { + "AUD": 143, + "BRL": 299, + "CAD": 137, + "CHF": 119, + "CLP": 66594, + "COP": 233940, + "DKK": 719, + "EUR": 107, + "GBP": 95, + "INR": 2759, + "MXN": 1739, + "NOK": 869, + "NZD": 143, + "PEN": 275, + "SEK": 869, + "SGD": 155, + "USD": 119 + }, + "50": { + "AUD": 131, + "BRL": 274, + "CAD": 125, + "CHF": 109, + "CLP": 61044, + "COP": 214445, + "DKK": 659, + "EUR": 98, + "GBP": 87, + "INR": 2529, + "MXN": 1594, + "NOK": 796, + "NZD": 131, + "PEN": 252, + "SEK": 796, + "SGD": 142, + "USD": 109 + } + } + } +} diff --git a/services/web/scripts/plan-prices/plans.mjs b/services/web/scripts/plan-prices/plans.mjs index aa5548f1d0..122b2c5683 100644 --- a/services/web/scripts/plan-prices/plans.mjs +++ b/services/web/scripts/plan-prices/plans.mjs @@ -135,6 +135,16 @@ function generateGroupPlans(workSheetJSON) { ) const sizes = ['2', '3', '4', '5', '10', '20', '50'] + const additionalLicenseAddOnLegacyPricesFilePath = path.resolve( + __dirname, + 'additional-license-add-on-legacy-prices.json' + ) + const additionalLicenseAddOnLegacyPricesFile = fs.readFileSync( + additionalLicenseAddOnLegacyPricesFilePath + ) + const additionalLicenseAddOnLegacyPrices = JSON.parse( + additionalLicenseAddOnLegacyPricesFile + ) const result = {} for (const type1 of ['educational', 'enterprise']) { @@ -152,6 +162,15 @@ function generateGroupPlans(workSheetJSON) { result[type1][type2][currency][size] = { price_in_cents: plan[currency] * 100, } + + const additionalLicenseAddOnLegacyPrice = + additionalLicenseAddOnLegacyPrices[type1][type2][size]?.[currency] + if (additionalLicenseAddOnLegacyPrice) { + Object.assign(result[type1][type2][currency][size], { + additional_license_legacy_price_in_cents: + additionalLicenseAddOnLegacyPrice * 100, + }) + } } } } diff --git a/services/web/test/unit/src/Subscription/RecurlyClientTests.js b/services/web/test/unit/src/Subscription/RecurlyClientTests.js index 35fd515d8c..fac2afc200 100644 --- a/services/web/test/unit/src/Subscription/RecurlyClientTests.js +++ b/services/web/test/unit/src/Subscription/RecurlyClientTests.js @@ -50,6 +50,7 @@ describe('RecurlyClient', function () { total: 16.5, periodStart: new Date(), periodEnd: new Date(), + createdAt: new Date(), }) this.recurlySubscription = { @@ -79,6 +80,7 @@ describe('RecurlyClient', function () { currency: this.subscription.currency, currentPeriodStartedAt: this.subscription.periodStart, currentPeriodEndsAt: this.subscription.periodEnd, + createdAt: this.subscription.createdAt, } this.recurlySubscriptionChange = new recurly.SubscriptionChange() diff --git a/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js b/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js index 66280e1a7f..c45f2d5580 100644 --- a/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js +++ b/services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js @@ -185,6 +185,38 @@ describe('RecurlyEntities', function () { ) }) + it('returns a change request with quantity and unit price specified', function () { + const { + RecurlySubscriptionChangeRequest, + RecurlySubscriptionAddOnUpdate, + } = this.RecurlyEntities + const quantity = 5 + const unitPrice = 10 + const changeRequest = this.subscription.getRequestForAddOnPurchase( + 'another-add-on', + quantity, + unitPrice + ) + expect(changeRequest).to.deep.equal( + new RecurlySubscriptionChangeRequest({ + subscription: this.subscription, + timeframe: 'now', + addOnUpdates: [ + new RecurlySubscriptionAddOnUpdate({ + code: this.addOn.code, + quantity: this.addOn.quantity, + unitPrice: this.addOn.unitPrice, + }), + new RecurlySubscriptionAddOnUpdate({ + code: 'another-add-on', + quantity, + unitPrice, + }), + ], + }) + ) + }) + it('throws a DuplicateAddOnError if the subscription already has the add-on', function () { expect(() => this.subscription.getRequestForAddOnPurchase(this.addOn.code) @@ -346,6 +378,7 @@ describe('RecurlyEntities', function () { total: 11.5, periodStart: new Date(), periodEnd: new Date(), + createdAt: new Date(), }) const change = new RecurlySubscriptionChange({ subscription, diff --git a/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs b/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs index d55d7fe9dd..3956d109af 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs +++ b/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs @@ -56,6 +56,7 @@ describe('SubscriptionGroupController', function () { .stub() .resolves(this.createSubscriptionChangeData), ensureFlexibleLicensingEnabled: sinon.stub().resolves(), + ensureAddSeatsEnabled: sinon.stub().resolves(), getGroupPlanUpgradePreview: sinon .stub() .resolves(this.previewSubscriptionChangeData), @@ -336,6 +337,9 @@ describe('SubscriptionGroupController', function () { this.SubscriptionGroupHandler.promises.ensureFlexibleLicensingEnabled .calledWith(this.plan) .should.equal(true) + this.SubscriptionGroupHandler.promises.ensureAddSeatsEnabled + .calledWith(this.plan) + .should.equal(true) page.should.equal('subscriptions/add-seats') props.subscriptionId.should.equal(this.subscriptionId) props.groupName.should.equal(this.subscription.teamName) @@ -375,6 +379,21 @@ describe('SubscriptionGroupController', function () { this.Controller.addSeatsToGroupSubscription(this.req, res) }) + + it('should redirect to subscription page when "add seats" is not enabled', function (done) { + this.SubscriptionGroupHandler.promises.ensureAddSeatsEnabled = sinon + .stub() + .rejects() + + const res = { + redirect: url => { + url.should.equal('/user/subscription') + done() + }, + } + + this.Controller.addSeatsToGroupSubscription(this.req, res) + }) }) describe('previewAddSeatsSubscriptionChange', function () { diff --git a/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js index aacfa916d3..aa3538e6cb 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js @@ -15,9 +15,12 @@ describe('SubscriptionGroupHandler', function () { this.subscription_id = '31DSd1123D' this.adding = 1 this.paymentMethod = { cardType: 'Visa', lastFour: '1111' } + this.RecurlyEntities = { + MEMBERS_LIMIT_ADD_ON_CODE: 'additional-license', + } this.localPlanInSettings = { - membersLimit: 2, - membersLimitAddOn: 'additional-license', + membersLimit: 5, + membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, } this.subscription = { @@ -37,12 +40,21 @@ describe('SubscriptionGroupHandler', function () { id: 123, addOns: [ { - code: 'additional-license', + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, quantity: 1, }, ], getRequestForAddOnUpdate: sinon.stub().returns(this.changeRequest), getRequestForGroupPlanUpgrade: sinon.stub().returns(this.changeRequest), + getRequestForAddOnPurchase: sinon.stub().returns(this.changeRequest), + getRequestForFlexibleLicensingGroupPlanUpgrade: sinon + .stub() + .returns(this.changeRequest), + createdAt: '2025-01-01T00:00:00Z', + currency: 'USD', + hasAddOn(code) { + return this.addOns.some(addOn => addOn.code === code) + }, } this.SubscriptionLocator = { @@ -81,7 +93,7 @@ describe('SubscriptionGroupHandler', function () { this.previewSubscriptionChange = { nextAddOns: [ { - code: 'additional-license', + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, quantity: this.recurlySubscription.addOns[0].quantity + this.adding, }, ], @@ -115,6 +127,19 @@ describe('SubscriptionGroupHandler', function () { }, } + this.GroupPlansData = { + enterprise: { + collaborator: { + USD: { + 5: { + price_in_cents: 10000, + additional_license_legacy_price_in_cents: 5000, + }, + }, + }, + }, + } + this.Handler = SandboxedModule.require(modulePath, { requires: { './SubscriptionUpdater': this.SubscriptionUpdater, @@ -126,7 +151,9 @@ describe('SubscriptionGroupHandler', function () { }, './RecurlyClient': this.RecurlyClient, './PlansLocator': this.PlansLocator, + './RecurlyEntities': this.RecurlyEntities, '../Authentication/SessionManager': this.SessionManager, + './GroupPlansData': this.GroupPlansData, }, }) }) @@ -274,8 +301,8 @@ describe('SubscriptionGroupHandler', function () { expect(data).to.deep.equal({ subscription: { groupPlan: true }, plan: { - membersLimit: 2, - membersLimitAddOn: 'additional-license', + membersLimit: 5, + membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, canUseFlexibleLicensing: true, }, recurlySubscription: this.recurlySubscription, @@ -293,60 +320,169 @@ describe('SubscriptionGroupHandler', function () { }) }) - afterEach(function () { - this.recurlySubscription.getRequestForAddOnUpdate - .calledWith( - 'additional-license', - this.recurlySubscription.addOns[0].quantity + this.adding + describe('has "additional-license" add-on', function () { + beforeEach(function () { + this.recurlySubscription.addOns = [ + { + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + quantity: 6, + }, + ] + this.prevQuantity = this.recurlySubscription.addOns[0].quantity + this.previewSubscriptionChange.nextAddOns = [ + { + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + quantity: this.prevQuantity + this.adding, + }, + ] + }) + + afterEach(function () { + sinon.assert.notCalled( + this.recurlySubscription.getRequestForAddOnPurchase ) - .should.equal(true) - }) - describe('previewAddSeatsSubscriptionChange', function () { - it('should return the subscription change preview', async function () { - const preview = - await this.Handler.promises.previewAddSeatsSubscriptionChange( - this.req - ) - - this.RecurlyClient.promises.getPaymentMethod - .calledWith(this.user_id) - .should.equal(true) - this.RecurlyClient.promises.previewSubscriptionChange - .calledWith(this.changeRequest) - .should.equal(true) - this.SubscriptionController.makeChangePreview + this.recurlySubscription.getRequestForAddOnUpdate .calledWith( - { - type: 'add-on-update', - addOn: { - code: 'additional-license', - quantity: - this.recurlySubscription.addOns[0].quantity + this.adding, - prevQuantity: this.adding, - }, - }, - this.previewSubscriptionChange, - this.paymentMethod + this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + this.recurlySubscription.addOns[0].quantity + this.adding ) .should.equal(true) - preview.should.equal(this.changePreview) + }) + + describe('previewAddSeatsSubscriptionChange', function () { + it('should return the subscription change preview', async function () { + const preview = + await this.Handler.promises.previewAddSeatsSubscriptionChange( + this.req + ) + this.RecurlyClient.promises.getPaymentMethod + .calledWith(this.user_id) + .should.equal(true) + this.RecurlyClient.promises.previewSubscriptionChange + .calledWith(this.changeRequest) + .should.equal(true) + this.SubscriptionController.makeChangePreview + .calledWith( + { + type: 'add-on-update', + addOn: { + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + quantity: + this.previewSubscriptionChange.nextAddOns[0].quantity, + prevQuantity: this.prevQuantity, + }, + }, + this.previewSubscriptionChange, + this.paymentMethod + ) + .should.equal(true) + preview.should.equal(this.changePreview) + }) + }) + + describe('createAddSeatsSubscriptionChange', function () { + it('should change the subscription', async function () { + const result = + await this.Handler.promises.createAddSeatsSubscriptionChange( + this.req + ) + + this.RecurlyClient.promises.applySubscriptionChangeRequest + .calledWith(this.changeRequest) + .should.equal(true) + this.SubscriptionHandler.promises.syncSubscription + .calledWith({ uuid: this.recurlySubscription.id }, this.user_id) + .should.equal(true) + expect(result).to.deep.equal({ + adding: this.req.body.adding, + }) + }) }) }) - describe('createAddSeatsSubscriptionChange', function () { - it('should change the subscription', async function () { - const result = - await this.Handler.promises.createAddSeatsSubscriptionChange(this.req) + describe('has no "additional-license" add-on', function () { + beforeEach(function () { + this.recurlySubscription.addOns = [] + this.prevQuantity = this.recurlySubscription.addOns[0]?.quantity ?? 0 + this.previewSubscriptionChange.nextAddOns = [ + { + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + quantity: this.prevQuantity + this.adding, + }, + ] + this.PlansLocator.findLocalPlanInSettings = sinon.stub().returns({ + ...this.localPlanInSettings, + planCode: 'group_collaborator_5_enterprise', + canUseFlexibleLicensing: true, + }) + }) - this.RecurlyClient.promises.applySubscriptionChangeRequest - .calledWith(this.changeRequest) - .should.equal(true) - this.SubscriptionHandler.promises.syncSubscription - .calledWith({ uuid: this.recurlySubscription.id }, this.user_id) - .should.equal(true) - expect(result).to.deep.equal({ - adding: this.req.body.adding, + afterEach(function () { + sinon.assert.notCalled( + this.recurlySubscription.getRequestForAddOnUpdate + ) + }) + + describe('previewAddSeatsSubscriptionChange', function () { + let preview + + afterEach(function () { + this.RecurlyClient.promises.getPaymentMethod + .calledWith(this.user_id) + .should.equal(true) + this.RecurlyClient.promises.previewSubscriptionChange + .calledWith(this.changeRequest) + .should.equal(true) + this.SubscriptionController.makeChangePreview + .calledWith( + { + type: 'add-on-update', + addOn: { + code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + quantity: + this.previewSubscriptionChange.nextAddOns[0].quantity, + prevQuantity: this.prevQuantity, + }, + }, + this.previewSubscriptionChange, + this.paymentMethod + ) + .should.equal(true) + preview.should.equal(this.changePreview) + }) + + it('should return the subscription change preview with legacy add-on price', async function () { + this.recurlySubscription.createdAt = '2025-01-01T00:00:00Z' + + preview = + await this.Handler.promises.previewAddSeatsSubscriptionChange( + this.req + ) + 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', async function () { + this.recurlySubscription.createdAt = '2030-01-01T00:00:00Z' + + preview = + await this.Handler.promises.previewAddSeatsSubscriptionChange( + this.req + ) + this.recurlySubscription.getRequestForAddOnPurchase + .calledWithExactly( + this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + this.adding, + undefined + ) + .should.equal(true) }) }) }) @@ -358,16 +494,32 @@ describe('SubscriptionGroupHandler', function () { this.Handler.promises.ensureFlexibleLicensingEnabled({ canUseFlexibleLicensing: false, }) - ).to.be.rejectedWith('The group plan does not support flexible licencing') + ).to.be.rejectedWith('The group plan does not support flexible licensing') + }) + + it('should not throw if the subscription can use flexible licensing', async function () { + await expect( + this.Handler.promises.ensureFlexibleLicensingEnabled({ + canUseFlexibleLicensing: true, + }) + ).to.not.be.rejected }) }) - it('should not throw if the subscription can use flexible licensing', async function () { - await expect( - this.Handler.promises.ensureFlexibleLicensingEnabled({ - canUseFlexibleLicensing: true, - }) - ).to.not.be.rejected + describe('ensureAddSeatsEnabled', function () { + it('should throw if the subscription can not use the "add seats" feature', async function () { + await expect( + this.Handler.promises.ensureAddSeatsEnabled({}) + ).to.be.rejectedWith('The group plan does not support adding seats') + }) + + it('should not throw if the subscription can use the "add seats" feature', async function () { + await expect( + this.Handler.promises.ensureAddSeatsEnabled({ + membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE, + }) + ).to.not.be.rejected + }) }) describe('upgradeGroupPlan', function () {