New plans page: Show initial price value upon first render for group price data (#7974)

* New plans page: Show initial price value upon first render

* fix wrong test on SubscriptionController and add new tests on SubscriptionHelper

GitOrigin-RevId: a339a97cff2df0728ba35885af8953c8a0e0b7c8
This commit is contained in:
M Fahru 2022-05-19 05:12:48 -04:00 committed by Copybot
parent 754ad5f40d
commit dceb6910c9
6 changed files with 529 additions and 7 deletions

View file

@ -20,6 +20,7 @@ const RecurlyEventHandler = require('./RecurlyEventHandler')
const { expressify } = require('../../util/promises')
const OError = require('@overleaf/o-error')
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
const SubscriptionHelper = require('./SubscriptionHelper')
const groupPlanModalOptions = Settings.groupPlanModalOptions
const validGroupPlanModalOptions = {
@ -95,6 +96,10 @@ async function plansPage(req, res) {
groupPlanModalOptions,
groupPlanModalDefaults,
useNewPlanName,
initialLocalizedGroupPrice:
SubscriptionHelper.generateInitialLocalizedGroupPrice(
recommendedCurrency
),
})
}

View file

@ -1,3 +1,6 @@
const GroupPlansData = require('./GroupPlansData')
const Settings = require('@overleaf/settings')
/**
* 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.
@ -6,6 +9,65 @@ function shouldPlanChangeAtTermEnd(oldPlan, newPlan) {
return oldPlan.price_in_cents > newPlan.price_in_cents
}
function generateInitialLocalizedGroupPrice(recommendedCurrency) {
const INITIAL_LICENSE_SIZE = 2
const currencySymbols = Settings.groupPlanModalOptions.currencySymbols
const recommendedCurrencySymbol = currencySymbols[recommendedCurrency]
// the price is in cents, so divide by 100 to get the value
const collaboratorPrice =
GroupPlansData.enterprise.collaborator[recommendedCurrency][
INITIAL_LICENSE_SIZE
].price_in_cents / 100
const collaboratorPricePerUser = collaboratorPrice / INITIAL_LICENSE_SIZE
const professionalPrice =
GroupPlansData.enterprise.professional[recommendedCurrency][
INITIAL_LICENSE_SIZE
].price_in_cents / 100
const professionalPricePerUser = professionalPrice / INITIAL_LICENSE_SIZE
switch (recommendedCurrency) {
case 'CHF': {
return {
price: {
collaborator: `${recommendedCurrencySymbol} ${collaboratorPrice}`,
professional: `${recommendedCurrencySymbol} ${professionalPrice}`,
},
pricePerUser: {
collaborator: `${recommendedCurrencySymbol} ${collaboratorPricePerUser}`,
professional: `${recommendedCurrencySymbol} ${professionalPricePerUser}`,
},
}
}
case 'DKK':
case 'NOK':
case 'SEK':
return {
price: {
collaborator: `${collaboratorPrice} ${recommendedCurrencySymbol}`,
professional: `${professionalPrice} ${recommendedCurrencySymbol}`,
},
pricePerUser: {
collaborator: `${collaboratorPricePerUser} ${recommendedCurrencySymbol}`,
professional: `${professionalPricePerUser} ${recommendedCurrencySymbol}`,
},
}
default: {
return {
price: {
collaborator: `${recommendedCurrencySymbol}${collaboratorPrice}`,
professional: `${recommendedCurrencySymbol}${professionalPrice}`,
},
pricePerUser: {
collaborator: `${recommendedCurrencySymbol}${collaboratorPricePerUser}`,
professional: `${recommendedCurrencySymbol}${professionalPricePerUser}`,
},
}
}
}
}
module.exports = {
shouldPlanChangeAtTermEnd,
generateInitialLocalizedGroupPrice,
}

View file

@ -114,7 +114,7 @@ mixin table_group
strike.plans-v2-table-annual-price-before-discount(data-ol-plans-v2-table-annual-price-before-discount)
+gen_localized_price_for_plan_view('collaborator', 'annual')
p.plans-v2-table-price
span(data-ol-group-price-per-user='collaborator') $0
span(data-ol-plans-v2-group-price-per-user='collaborator') #{initialLocalizedGroupPrice.pricePerUser.collaborator}
p.plans-v2-table-price-period-label
| per user / year
.plans-v2-table-btn-buy-container-mobile
@ -123,7 +123,7 @@ mixin table_group
ul.plans-v2-table-th-content-benefit
li #{translate("up_to")} !{translate("x_collaborators_per_project", {collaboratorsCount: '10'})}
li
span.plans-v2-group-total-price(data-ol-plans-v2-group-total-price='collaborator') $0
span.plans-v2-group-total-price(data-ol-plans-v2-group-total-price='collaborator') #{initialLocalizedGroupPrice.price.collaborator}
span  #{translate("total_per_year_lowercase")}
.plans-v2-table-btn-buy-container-desktop
+btn_buy_group_collaborator()
@ -137,7 +137,7 @@ mixin table_group
strike.plans-v2-table-annual-price-before-discount(data-ol-plans-v2-table-annual-price-before-discount)
+gen_localized_price_for_plan_view('professional', 'annual')
p.plans-v2-table-price
span(data-ol-group-price-per-user='professional') $0
span(data-ol-plans-v2-group-price-per-user='professional') #{initialLocalizedGroupPrice.pricePerUser.professional}
p.plans-v2-table-price-period-label
| per user / year
.plans-v2-table-btn-buy-container-mobile
@ -146,7 +146,7 @@ mixin table_group
ul.plans-v2-table-th-content-benefit
li #{translate("unlimited_collaborators_in_each_project")}
li
span.plans-v2-group-total-price(data-ol-plans-v2-group-total-price='professional') $0
span.plans-v2-group-total-price(data-ol-plans-v2-group-total-price='professional') #{initialLocalizedGroupPrice.price.professional}
span  #{translate("total_per_year_lowercase")}
.plans-v2-table-btn-buy-container-desktop
+btn_buy_group_professional()

View file

@ -60,11 +60,11 @@ export function updateGroupPricing() {
).innerText = displayPriceCollaborator
document.querySelector(
'[data-ol-group-price-per-user="collaborator"]'
'[data-ol-plans-v2-group-price-per-user="collaborator"]'
).innerText = displayPerUserPriceCollaborator
document.querySelector(
'[data-ol-group-price-per-user="professional"]'
'[data-ol-plans-v2-group-price-per-user="professional"]'
).innerText = displayPerUserPriceProfessional
// educational discount can only be activated if numberOfLicenses is > 10

View file

@ -122,11 +122,15 @@ describe('SubscriptionController', function () {
getAssignment: sinon.stub().resolves({ variant: 'default' }),
},
}
this.SubscriptionHelper = {
generateInitialLocalizedGroupPrice: sinon.stub(),
}
this.SubscriptionController = SandboxedModule.require(modulePath, {
requires: {
'../SplitTests/SplitTestHandler': this.SplitTestV2Hander,
'../Authentication/SessionManager': this.SessionManager,
'./SubscriptionHandler': this.SubscriptionHandler,
'./SubscriptionHelper': this.SubscriptionHelper,
'./PlansLocator': this.PlansLocator,
'./SubscriptionViewModelBuilder': this.SubscriptionViewModelBuilder,
'./LimitationsManager': this.LimitationsManager,

View file

@ -31,7 +31,84 @@ const plans = {
describe('SubscriptionHelper', function () {
beforeEach(function () {
this.SubscriptionHelper = SandboxedModule.require(modulePath)
this.INITIAL_LICENSE_SIZE = 2
this.mockCollaboratorPrice = 2000
this.mockProfessionalPrice = 4000
this.settings = {
groupPlanModalOptions: {
currencySymbols: {
USD: '$',
CHF: 'Fr',
DKK: 'kr',
NOK: 'kr',
SEK: 'kr',
},
},
}
this.GroupPlansData = {
enterprise: {
collaborator: {
USD: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockCollaboratorPrice,
},
},
CHF: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockCollaboratorPrice,
},
},
DKK: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockCollaboratorPrice,
},
},
NOK: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockCollaboratorPrice,
},
},
SEK: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockCollaboratorPrice,
},
},
},
professional: {
USD: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockProfessionalPrice,
},
},
CHF: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockProfessionalPrice,
},
},
DKK: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockProfessionalPrice,
},
},
NOK: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockProfessionalPrice,
},
},
SEK: {
[this.INITIAL_LICENSE_SIZE]: {
price_in_cents: this.mockProfessionalPrice,
},
},
},
},
}
this.SubscriptionHelper = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': this.settings,
'./GroupPlansData': this.GroupPlansData,
},
})
})
describe('shouldPlanChangeAtTermEnd', function () {
@ -71,4 +148,378 @@ describe('SubscriptionHelper', function () {
expect(changeAtTermEnd).to.be.true
})
})
describe('generateInitialLocalizedGroupPrice', function () {
describe('collaborator plan', function () {
beforeEach(function () {
this.plan = 'collaborator'
})
describe('for CHF currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'CHF'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.recommendedCurrencySymbol} ${this.expectedPrice}`
const {
price: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${this.recommendedCurrencySymbol} ${expectedPricePerUser}`
const {
pricePerUser: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for DKK currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'DKK'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.expectedPrice} ${this.recommendedCurrencySymbol}`
const {
price: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${expectedPricePerUser} ${this.recommendedCurrencySymbol}`
const {
pricePerUser: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for SEK currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'SEK'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.expectedPrice} ${this.recommendedCurrencySymbol}`
const {
price: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${expectedPricePerUser} ${this.recommendedCurrencySymbol}`
const {
pricePerUser: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for NOK currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'NOK'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.expectedPrice} ${this.recommendedCurrencySymbol}`
const {
price: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${expectedPricePerUser} ${this.recommendedCurrencySymbol}`
const {
pricePerUser: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for other supported currencies', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'USD'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.recommendedCurrencySymbol}${this.expectedPrice}`
const {
price: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${this.recommendedCurrencySymbol}${expectedPricePerUser}`
const {
pricePerUser: { collaborator },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(collaborator).to.be.equal(expectedLocalizedPricePerUser)
})
})
})
describe('professional plan plan', function () {
beforeEach(function () {
this.plan = 'professional'
})
describe('for CHF currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'CHF'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.recommendedCurrencySymbol} ${this.expectedPrice}`
const {
price: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${this.recommendedCurrencySymbol} ${expectedPricePerUser}`
const {
pricePerUser: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for DKK currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'DKK'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.expectedPrice} ${this.recommendedCurrencySymbol}`
const {
price: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${expectedPricePerUser} ${this.recommendedCurrencySymbol}`
const {
pricePerUser: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for SEK currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'SEK'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.expectedPrice} ${this.recommendedCurrencySymbol}`
const {
price: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${expectedPricePerUser} ${this.recommendedCurrencySymbol}`
const {
pricePerUser: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for NOK currency', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'NOK'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.expectedPrice} ${this.recommendedCurrencySymbol}`
const {
price: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${expectedPricePerUser} ${this.recommendedCurrencySymbol}`
const {
pricePerUser: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPricePerUser)
})
})
describe('for other supported currencies', function () {
beforeEach(function () {
this.mockRecommendedCurrency = 'USD'
this.recommendedCurrencySymbol =
this.settings.groupPlanModalOptions.currencySymbols[
this.mockRecommendedCurrency
]
this.expectedPrice =
this.GroupPlansData.enterprise[this.plan][
this.mockRecommendedCurrency
][this.INITIAL_LICENSE_SIZE].price_in_cents / 100
})
it('should return the correct localized price', function () {
const expectedLocalizedPrice = `${this.recommendedCurrencySymbol}${this.expectedPrice}`
const {
price: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPrice)
})
it('should return the correct localized price per user', function () {
const expectedPricePerUser =
this.expectedPrice / this.INITIAL_LICENSE_SIZE
const expectedLocalizedPricePerUser = `${this.recommendedCurrencySymbol}${expectedPricePerUser}`
const {
pricePerUser: { professional },
} = this.SubscriptionHelper.generateInitialLocalizedGroupPrice(
this.mockRecommendedCurrency
)
expect(professional).to.be.equal(expectedLocalizedPricePerUser)
})
})
})
})
})