Implement checks for user eligibility when switching plans (#24276)

* Convert updateSubscription controller to async/await

* Move updateSubscription to subscription module

* Validate if user is eligible to change plan

GitOrigin-RevId: ce538429cd5a3b93acabdc046f1a8b164ac02301
This commit is contained in:
Thomas Mees 2025-03-13 13:04:39 +01:00 committed by Copybot
parent e6371ec197
commit 84996ea88c
6 changed files with 36 additions and 63 deletions

View file

@ -458,30 +458,6 @@ async function previewSubscription(req, res, next) {
res.render('subscriptions/preview-change', { changePreview })
}
function updateSubscription(req, res, next) {
const origin = req && req.query ? req.query.origin : null
const user = SessionManager.getSessionUser(req.session)
const planCode = req.body.plan_code
if (planCode == null) {
const err = new Error('plan_code is not defined')
logger.warn(
{ userId: user._id, err, planCode, origin, body: req.body },
'[Subscription] error in updateSubscription form'
)
return next(err)
}
logger.debug({ planCode, userId: user._id }, 'updating subscription')
SubscriptionHandler.updateSubscription(user, planCode, null, function (err) {
if (err) {
OError.tag(err, 'something went wrong updating subscription', {
user_id: user._id,
})
return next(err)
}
res.redirect('/user/subscription')
})
}
function cancelPendingSubscriptionChange(req, res, next) {
const user = SessionManager.getSessionUser(req.session)
logger.debug({ userId: user._id }, 'canceling pending subscription change')
@ -799,7 +775,6 @@ module.exports = {
canceledSubscription: expressify(canceledSubscription),
cancelV1Subscription,
previewSubscription: expressify(previewSubscription),
updateSubscription,
cancelPendingSubscriptionChange,
updateAccountEmailAddress,
reactivateSubscription,

View file

@ -183,12 +183,6 @@ export default {
RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
SubscriptionController.previewSubscription
)
webRouter.post(
'/user/subscription/update',
AuthenticationController.requireLogin(),
RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
SubscriptionController.updateSubscription
)
webRouter.get(
'/user/subscription/addon/:addOnCode/add',
AuthenticationController.requireLogin(),

View file

@ -1543,6 +1543,7 @@
"sorry_there_are_no_experiments": "",
"sorry_there_was_an_issue_adding_x_users_to_your_subscription": "",
"sorry_there_was_an_issue_upgrading_your_subscription": "",
"sorry_you_can_only_change_to_group_from_trial_via_support": "",
"sorry_your_table_cant_be_displayed_at_the_moment": "",
"sort_by": "",
"sort_by_x": "",

View file

@ -1,10 +1,20 @@
import { useTranslation } from 'react-i18next'
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import isInFreeTrial from '../../../../../util/is-in-free-trial'
import { RecurlySubscription } from '../../../../../../../../../types/subscription/dashboard/subscription'
export function ChangeToGroupPlan() {
const { t } = useTranslation()
const { handleOpenModal } = useSubscriptionDashboardContext()
const { handleOpenModal, personalSubscription } =
useSubscriptionDashboardContext()
// TODO: Better way to get RecurlySubscription/trial_ends_at
const subscription =
personalSubscription && 'recurly' in personalSubscription
? (personalSubscription as RecurlySubscription)
: null
const handleClick = () => {
handleOpenModal('change-to-group')
@ -15,9 +25,27 @@ export function ChangeToGroupPlan() {
<h2 style={{ marginTop: 0 }}>{t('looking_multiple_licenses')}</h2>
<p style={{ margin: 0 }}>{t('reduce_costs_group_licenses')}</p>
<br />
<OLButton variant="primary" onClick={handleClick}>
{t('change_to_group_plan')}
</OLButton>
{isInFreeTrial(subscription?.recurly?.trial_ends_at) ? (
<>
<OLTooltip
id="disabled-change-to-group-plan"
description={t(
'sorry_you_can_only_change_to_group_from_trial_via_support'
)}
overlayProps={{ placement: 'top' }}
>
<div>
<OLButton variant="primary" disabled>
{t('change_to_group_plan')}
</OLButton>
</div>
</OLTooltip>
</>
) : (
<OLButton variant="primary" onClick={handleClick}>
{t('change_to_group_plan')}
</OLButton>
)}
</div>
)
}

View file

@ -2023,6 +2023,7 @@
"sorry_there_was_an_issue_adding_x_users_to_your_subscription": "Sorry, there was an issue adding __count__ users to your subscription. Please <0>contact our Support team</0> for help.",
"sorry_there_was_an_issue_upgrading_your_subscription": "Sorry, there was an issue upgrading your subscription. Please <0>contact our Support team</0> for help.",
"sorry_this_account_has_been_suspended": "Sorry, this account has been suspended.",
"sorry_you_can_only_change_to_group_from_trial_via_support": "Sorry, you can only change to a group plan during a free trial by contacting support.",
"sorry_your_table_cant_be_displayed_at_the_moment": "Sorry, your table cant be displayed at the moment.",
"sorry_your_token_expired": "Sorry, your token expired",
"sort_by": "Sort by",

View file

@ -175,9 +175,9 @@ describe('SubscriptionController', function () {
recordEventForSession: sinon.stub(),
setUserPropertyForUser: sinon.stub(),
}),
'../../infrastructure/Modules': {
'../../infrastructure/Modules': (this.Modules = {
promises: { hooks: { fire: sinon.stub().resolves() } },
},
}),
'../../infrastructure/Features': this.Features,
'../../util/currency': (this.currency = {
formatCurrency: sinon.stub(),
@ -293,32 +293,6 @@ describe('SubscriptionController', function () {
})
})
describe('updateSubscription via post', function () {
beforeEach(function (done) {
this.res = {
redirect() {
done()
},
}
sinon.spy(this.res, 'redirect')
this.plan_code = '1234'
this.req.body.plan_code = this.plan_code
this.SubscriptionController.updateSubscription(this.req, this.res)
})
it('should send the user and subscriptionId to the handler', function (done) {
this.SubscriptionHandler.updateSubscription
.calledWith(this.user, this.plan_code)
.should.equal(true)
done()
})
it('should redurect to the subscription page', function (done) {
this.res.redirect.calledWith('/user/subscription').should.equal(true)
done()
})
})
describe('updateAccountEmailAddress via put', function () {
it('should send the user and subscriptionId to RecurlyWrapper', function () {
this.res.sendStatus = sinon.spy()