Merge pull request #26934 from overleaf/ls-support-individual-to-group-plan-upgrade

Support individual to group plan upgrade in Stripe

GitOrigin-RevId: 24cbe7bd6de86a4d9410e1abc49b6457e0871f40
This commit is contained in:
Liangjun Song
2025-07-16 08:05:20 +00:00
committed by Copybot
parent 1375f695d3
commit 9e22ed9c3f
12 changed files with 364 additions and 91 deletions
@@ -27,6 +27,10 @@ describe('PaymentProviderEntities', function () {
{ planCode: 'cheap-plan', price_in_cents: 500 },
{ planCode: 'regular-plan', price_in_cents: 1000 },
{ planCode: 'premium-plan', price_in_cents: 2000 },
{
planCode: 'group_collaborator_10_enterprise',
price_in_cents: 10000,
},
],
features: [],
}
@@ -81,8 +85,11 @@ describe('PaymentProviderEntities', function () {
it('returns a change request for upgrades', function () {
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForPlanChange('premium-plan')
const changeRequest = this.subscription.getRequestForPlanChange(
'premium-plan',
1,
this.subscription.shouldPlanChangeAtTermEnd('premium-plan')
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
@@ -95,8 +102,11 @@ describe('PaymentProviderEntities', function () {
it('returns a change request for downgrades', function () {
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
const changeRequest = this.subscription.getRequestForPlanChange(
'cheap-plan',
1,
this.subscription.shouldPlanChangeAtTermEnd('cheap-plan')
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
@@ -112,8 +122,11 @@ describe('PaymentProviderEntities', function () {
this.subscription.trialPeriodEnd = fiveDaysFromNow
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
const changeRequest = this.subscription.getRequestForPlanChange(
'cheap-plan',
1,
this.subscription.shouldPlanChangeAtTermEnd('cheap-plan')
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
@@ -127,8 +140,11 @@ describe('PaymentProviderEntities', function () {
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
this.addOn.code = AI_ADD_ON_CODE
const changeRequest =
this.subscription.getRequestForPlanChange('premium-plan')
const changeRequest = this.subscription.getRequestForPlanChange(
'premium-plan',
1,
this.subscription.shouldPlanChangeAtTermEnd('premium-plan')
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
@@ -148,8 +164,11 @@ describe('PaymentProviderEntities', function () {
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
this.addOn.code = AI_ADD_ON_CODE
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
const changeRequest = this.subscription.getRequestForPlanChange(
'cheap-plan',
1,
this.subscription.shouldPlanChangeAtTermEnd('cheap-plan')
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
@@ -170,8 +189,11 @@ describe('PaymentProviderEntities', function () {
this.PaymentProviderEntities
this.subscription.planCode = 'assistant-annual'
this.subscription.addOns = []
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
const changeRequest = this.subscription.getRequestForPlanChange(
'cheap-plan',
1,
this.subscription.shouldPlanChangeAtTermEnd('cheap-plan')
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
@@ -186,6 +208,63 @@ describe('PaymentProviderEntities', function () {
})
)
})
it('upgrade from individual to group plan for Stripe subscription', function () {
this.subscription.service = 'stripe-uk'
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
const changeRequest = this.subscription.getRequestForPlanChange(
'group_collaborator',
10,
this.subscription.shouldPlanChangeAtTermEnd(
'group_collaborator_10_enterprise'
)
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'group_collaborator',
addOnUpdates: [
new PaymentProviderSubscriptionAddOnUpdate({
code: 'additional-license',
quantity: 10,
}),
],
})
)
})
it('upgrade from individual to group plan and preserves the AI add-on for Stripe subscription', function () {
this.subscription.service = 'stripe-uk'
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
this.addOn.code = AI_ADD_ON_CODE
const changeRequest = this.subscription.getRequestForPlanChange(
'group_collaborator',
10,
this.subscription.shouldPlanChangeAtTermEnd(
'group_collaborator_10_enterprise'
)
)
expect(changeRequest).to.deep.equal(
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'group_collaborator',
addOnUpdates: [
new PaymentProviderSubscriptionAddOnUpdate({
code: 'additional-license',
quantity: 10,
}),
new PaymentProviderSubscriptionAddOnUpdate({
code: AI_ADD_ON_CODE,
quantity: 1,
}),
],
})
)
})
})
describe('getRequestForAddOnPurchase()', function () {
@@ -266,4 +266,39 @@ describe('PlansLocator', function () {
expect(period).to.equal('annual')
})
})
describe('convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded', function () {
it('returns original plan name for non-group plan codes', function () {
expect(
this.PlansLocator.convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded(
'professional'
)
).to.deep.equal({
planCode: 'professional',
quantity: 1,
})
})
it('converts Recurly enterprise group plan codes to Stripe group plan codes', function () {
expect(
this.PlansLocator.convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded(
'group_collaborator_10_enterprise'
)
).to.deep.equal({
planCode: 'group_collaborator',
quantity: 10,
})
})
it('converts Recurly educational group plan codes to Stripe group plan codes', function () {
expect(
this.PlansLocator.convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded(
'group_professional_10_educational'
)
).to.deep.equal({
planCode: 'group_professional_educational',
quantity: 10,
})
})
})
})
@@ -150,6 +150,11 @@ describe('SubscriptionViewModelBuilder', function () {
this.PlansLocator = {
findLocalPlanInSettings: sinon.stub(),
}
this.SplitTestHandler = {
promises: {
getAssignmentForUser: sinon.stub().resolves({ variant: 'default' }),
},
}
this.SubscriptionViewModelBuilder = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': this.Settings,
@@ -168,6 +173,7 @@ describe('SubscriptionViewModelBuilder', function () {
'./V1SubscriptionManager': {},
'../Publishers/PublishersGetter': this.PublishersGetter,
'./SubscriptionHelper': SubscriptionHelper,
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
},
})
@@ -659,6 +665,9 @@ describe('SubscriptionViewModelBuilder', function () {
describe('isEligibleForGroupPlan', function () {
it('is false for Stripe subscriptions', async function () {
this.paymentRecord.service = 'stripe-us'
this.Modules.promises.hooks.fire
.withArgs('canUpgradeFromIndividualToGroup')
.resolves([false])
const result =
await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
this.user