[web] Prevent subscription downgrades (#19895)

* Notify support when subscription deletion skipped

GitOrigin-RevId: b0ff548b4e1bf5843a96885b3176fdf11a49a2e1
This commit is contained in:
Miguel Serrano 2024-11-11 11:18:06 +01:00 committed by Copybot
parent 77cfdb391c
commit 1b80f172d7
2 changed files with 163 additions and 1 deletions

View file

@ -274,6 +274,27 @@ async function _notifySupportSubscriptionDeletionSkipped(
await Modules.promises.hooks.fire('sendSupportRequest', data)
}
async function _notifySubscriptionDowngradeSkipped(
subscription,
recurlySubscription,
subject
) {
const adminUrl = `${Settings.adminUrl + '/admin/user/' + subscription.admin_id}`
const groupUrl = `${Settings.adminUrl + '/admin/group/' + subscription._id}`
let message = `\n**Recurly account:** <a href="${recurlySubscription?.account?.url}">${recurlySubscription?.account?.url}</a>`
message += `\n**Group admin:** <a href="${adminUrl}">${adminUrl}</a>`
message += `\n**Group:** <a href="${groupUrl}">${groupUrl}</a>`
const data = {
subject,
inbox: 'support',
tags: 'Group subscription',
message,
}
await Modules.promises.hooks.fire('sendSupportRequest', data)
}
async function updateSubscriptionFromRecurly(
recurlySubscription,
subscription,
@ -353,6 +374,8 @@ async function updateSubscriptionFromRecurly(
return
}
const currentSubscriptionPlanCode = subscription.planCode
const addOns = recurlySubscription?.subscription_add_ons?.map(addOn => {
return {
addOnCode: addOn.add_on_code,
@ -371,13 +394,65 @@ async function updateSubscriptionFromRecurly(
}
if (plan.groupPlan) {
if (Settings.preventSubscriptionPlanDowngrade) {
// We're preventing professional to standard downgrade.
// See https://github.com/overleaf/internal/issues/19852
const isProfessionalToStandardDowngrade =
currentSubscriptionPlanCode.includes('professional') &&
plan.planCode.includes('collaborator') &&
!plan.planCode.includes('_ibis') &&
!plan.planCode.includes('_heron') &&
plan.planCode !== 'collaborator-annual_free_trial'
if (isProfessionalToStandardDowngrade) {
try {
await _notifySubscriptionDowngradeSkipped(
subscription,
recurlySubscription,
'Skipped professional to standard downgrade'
)
} catch (e) {
logger.warn(
{ subscriptionId: subscription._id },
'unable to send notification to support that professional to standard downgrade was skipped'
)
}
subscription.planCode = currentSubscriptionPlanCode
}
}
if (!subscription.groupPlan) {
subscription.member_ids = subscription.member_ids || []
subscription.member_ids.push(subscription.admin_id)
}
subscription.groupPlan = true
subscription.membersLimit = plan.membersLimit
if (Settings.preventSubscriptionPlanDowngrade) {
// We're preventing automatically downgrading of group member limit
// See https://github.com/overleaf/internal/issues/19852
if (
!subscription.membersLimit ||
subscription.membersLimit < plan.membersLimit
) {
subscription.membersLimit = plan.membersLimit
} else {
try {
await _notifySubscriptionDowngradeSkipped(
subscription,
recurlySubscription,
'Skipped group size downgrade'
)
} catch (e) {
logger.warn(
{ subscriptionId: subscription._id },
'unable to send notification to support that group size downgrade was skipped'
)
}
}
} else {
subscription.membersLimit = plan.membersLimit
}
// Some plans allow adding more seats than the base plan provides.
// This is recorded as a subscription add on.

View file

@ -431,6 +431,93 @@ describe('SubscriptionUpdater', function () {
assert.notEqual(this.subscription.groupPlan, true)
})
describe('prevent subscription plan downgrade', function () {
describe('prevent professional to standard plan downgrade', function () {
beforeEach(function () {
this.recurlyPlan.groupPlan = true
this.recurlyPlan.planCode = 'collaborator'
this.recurlySubscription.plan.plan_code = 'collaborator'
this.groupSubscription.planCode = 'professional'
})
it('should not downgrade from professional to standard plan when preventSubscriptionPlanDowngrade=true', async function () {
this.Settings.preventSubscriptionPlanDowngrade = true
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.groupSubscription,
{}
)
this.groupSubscription.planCode.should.equal('professional')
const adminUrl = `${this.Settings.adminUrl + '/admin/user/' + this.groupSubscription.admin_id}`
const groupUrl = `${this.Settings.adminUrl + '/admin/group/' + this.groupSubscription._id}`
let message = `\n**Recurly account:** <a href="${this.recurlySubscription?.account?.url}">${this.recurlySubscription?.account?.url}</a>`
message += `\n**Group admin:** <a href="${adminUrl}">${adminUrl}</a>`
message += `\n**Group:** <a href="${groupUrl}">${groupUrl}</a>`
expect(this.Modules.promises.hooks.fire).to.have.been.calledOnce
expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
'sendSupportRequest',
{
subject: 'Skipped professional to standard downgrade',
inbox: 'support',
tags: 'Group subscription',
message,
}
)
})
it('should downgrade from professional to standard plan when preventSubscriptionPlanDowngrade=false', async function () {
this.Settings.preventSubscriptionPlanDowngrade = false
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.groupSubscription,
{}
)
this.groupSubscription.planCode.should.equal('collaborator')
})
})
describe('prevent decreasing group size', function () {
beforeEach(function () {
this.groupSubscription.membersLimit = 3
this.recurlyPlan.groupPlan = true
this.recurlyPlan.membersLimit = 2
})
it('should not reduce member limits when preventSubscriptionPlanDowngrade=true', async function () {
this.Settings.preventSubscriptionPlanDowngrade = true
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.groupSubscription,
{}
)
this.groupSubscription.membersLimit.should.equal(3)
const adminUrl = `${this.Settings.adminUrl + '/admin/user/' + this.groupSubscription.admin_id}`
const groupUrl = `${this.Settings.adminUrl + '/admin/group/' + this.groupSubscription._id}`
let message = `\n**Recurly account:** <a href="${this.recurlySubscription?.account?.url}">${this.recurlySubscription?.account?.url}</a>`
message += `\n**Group admin:** <a href="${adminUrl}">${adminUrl}</a>`
message += `\n**Group:** <a href="${groupUrl}">${groupUrl}</a>`
expect(this.Modules.promises.hooks.fire).to.have.been.calledOnce
expect(this.Modules.promises.hooks.fire).to.have.been.calledWith(
'sendSupportRequest',
{
subject: 'Skipped group size downgrade',
inbox: 'support',
tags: 'Group subscription',
message,
}
)
})
it('should reduce member limits when preventSubscriptionPlanDowngrade=false', async function () {
this.Settings.preventSubscriptionPlanDowngrade = false
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
this.recurlySubscription,
this.groupSubscription,
{}
)
this.groupSubscription.membersLimit.should.equal(2)
})
})
})
describe('when the plan allows adding more seats', function () {
beforeEach(function () {
this.membersLimitAddOn = 'add_on1'