diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index c9ecea4dbb..68a2f89cd3 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -120,7 +120,8 @@ async function _getIndividualFeatures(userId) { await SubscriptionLocator.promises.getUsersSubscription(userId) if ( subscription == null || - SubscriptionHelper.getPaidSubscriptionState(subscription) === 'paused' + SubscriptionHelper.getPaidSubscriptionState(subscription) === 'paused' || + subscription.userFeaturesDisabled ) { return {} } @@ -140,7 +141,9 @@ async function _getIndividualFeatures(userId) { async function _getGroupFeatureSets(userId) { const subs = await SubscriptionLocator.promises.getGroupSubscriptionsMemberOf(userId) - return (subs || []).map(_subscriptionToFeatures) + return (subs || []) + .filter(sub => sub.userFeaturesDisabled !== true) + .map(_subscriptionToFeatures) } async function _getFeaturesOverrides(user) { diff --git a/services/web/app/src/Features/Subscription/SubscriptionLocator.js b/services/web/app/src/Features/Subscription/SubscriptionLocator.js index bb9013dcc5..cd3de5e44c 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionLocator.js +++ b/services/web/app/src/Features/Subscription/SubscriptionLocator.js @@ -94,7 +94,7 @@ const SubscriptionLocator = { async getGroupSubscriptionsMemberOf(userId) { return await Subscription.find( { member_ids: userId }, - { _id: 1, planCode: 1 } + { _id: 1, planCode: 1, userFeaturesDisabled: 1 } ) }, diff --git a/services/web/app/src/models/Subscription.js b/services/web/app/src/models/Subscription.js index b4fa238e59..c7606d39ea 100644 --- a/services/web/app/src/models/Subscription.js +++ b/services/web/app/src/models/Subscription.js @@ -45,6 +45,7 @@ const SubscriptionSchema = new Schema( groupSSO: { type: Boolean, default: true }, domainCapture: { type: Boolean, default: false }, }, + userFeaturesDisabled: Boolean, addOns: Schema.Types.Mixed, overleaf: { id: { diff --git a/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js b/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js index 86d6524ee6..c8113971f4 100644 --- a/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js +++ b/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js @@ -135,6 +135,49 @@ describe('FeaturesUpdater', function () { }) describe('computeFeatures', function () { + describe('when userFeaturesDisabled is true for individual plan', function () { + beforeEach(function () { + this.SubscriptionLocator.promises.getUsersSubscription + .withArgs(this.user._id) + .resolves({ + planCode: 'individual-plan', + userFeaturesDisabled: true, + groupPlan: false, + addOns: [this.aiAddOn], + }) + }) + + it('removes all individual plan features', async function () { + const features = await this.FeaturesUpdater.promises.computeFeatures( + this.user._id + ) + expect(features).to.deep.equal({ default: 'features' }) + }) + }) + + describe('when userFeaturesDisabled is true for group plan', function () { + beforeEach(function () { + const groupSubscription = { + planCode: 'group-plan-1', + userFeaturesDisabled: true, + groupPlan: true, + addOns: [this.aiAddOn], + } + this.SubscriptionLocator.promises.getUsersSubscription + .withArgs(this.user._id) + .resolves(groupSubscription) + this.SubscriptionLocator.promises.getGroupSubscriptionsMemberOf + .withArgs(this.user._id) + .resolves([groupSubscription]) + }) + + it('removes all group plan features', async function () { + const features = await this.FeaturesUpdater.promises.computeFeatures( + this.user._id + ) + expect(features).to.deep.equal({ default: 'features' }) + }) + }) beforeEach(function () { this.SubscriptionLocator.promises.getUsersSubscription .withArgs(this.user._id) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index 56e525a2c8..7acdd7d81e 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -82,6 +82,14 @@ export type InvoiceVoidedWebhookEvent = { request: Stripe.Event.Request } +export type InvoiceOverdueWebhookEvent = { + type: 'invoice.overdue' + data: { + object: Stripe.Invoice + } + request: Stripe.Event.Request +} + export type CustomerSubscriptionWebhookEvent = | CustomerSubscriptionUpdatedWebhookEvent | CustomerSubscriptionCreatedWebhookEvent @@ -92,3 +100,4 @@ export type WebhookEvent = | InvoicePaidWebhookEvent | InvoiceVoidedWebhookEvent | PaymentIntentPaymentFailedWebhookEvent + | InvoiceOverdueWebhookEvent