Ver código fonte

Merge pull request #8289 from overleaf/ta-redundant-subscription-email

Create Redundant Subscription Notification on Email Confirmation

GitOrigin-RevId: 77baab93ebaae85d09681051641e663bb680c17e
Timothée Alby 4 anos atrás
pai
commit
e6c7025813

+ 3 - 12
services/web/app/src/Features/User/SAMLIdentityManager.js

@@ -262,19 +262,10 @@ async function linkAccounts(userId, samlData, auditLog) {
     await _removeIdentifier(userId, providerId)
     throw error
   }
-  await UserUpdater.promises.confirmEmail(userId, institutionEmail) // will set confirmedAt if not set, and will always update reconfirmedAt
+  await UserUpdater.promises.confirmEmail(userId, institutionEmail, {
+    entitlement: hasEntitlement,
+  }) // will set confirmedAt if not set, and will always update reconfirmedAt
   await _sendLinkedEmail(userId, providerName, institutionEmail)
-  // update v1 affiliations record
-  if (hasEntitlement) {
-    await InstitutionsAPI.promises.addEntitlement(userId, institutionEmail)
-    try {
-      await redundantSubscription(userId, providerId, providerName)
-    } catch (error) {
-      logger.err({ err: error }, 'error checking redundant subscription')
-    }
-  } else {
-    await InstitutionsAPI.promises.removeEntitlement(userId, institutionEmail)
-  }
 }
 
 async function unlinkAccounts(

+ 44 - 4
services/web/app/src/Features/User/UserUpdater.js

@@ -14,6 +14,8 @@ const NewsletterManager = require('../Newsletter/NewsletterManager')
 const RecurlyWrapper = require('../Subscription/RecurlyWrapper')
 const UserAuditLogHandler = require('./UserAuditLogHandler')
 const AnalyticsManager = require('../Analytics/AnalyticsManager')
+const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
+const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
 const _ = require('lodash')
 
 async function _sendSecurityAlertPrimaryEmailChanged(userId, oldEmail, email) {
@@ -223,7 +225,7 @@ async function setDefaultEmailAddress(
   }
 }
 
-async function confirmEmail(userId, email) {
+async function confirmEmail(userId, email, affiliationOptions) {
   // used for initial email confirmation (non-SSO and SSO)
   // also used for reconfirmation of non-SSO emails
   const confirmedAt = new Date()
@@ -234,9 +236,13 @@ async function confirmEmail(userId, email) {
   logger.debug({ userId, email }, 'confirming user email')
 
   try {
-    await InstitutionsAPI.promises.addAffiliation(userId, email, {
-      confirmedAt,
-    })
+    affiliationOptions = affiliationOptions || {}
+    affiliationOptions.confirmedAt = confirmedAt
+    await InstitutionsAPI.promises.addAffiliation(
+      userId,
+      email,
+      affiliationOptions
+    )
   } catch (error) {
     throw OError.tag(error, 'problem adding affiliation while confirming email')
   }
@@ -268,6 +274,40 @@ async function confirmEmail(userId, email) {
     throw new Errors.NotFoundError('user id and email do no match')
   }
   await FeaturesUpdater.promises.refreshFeatures(userId, 'confirm-email')
+  try {
+    await maybeCreateRedundantSubscriptionNotification(userId, email)
+  } catch (error) {
+    logger.err(
+      { err: error },
+      'error checking redundant subscription on email confirmation'
+    )
+  }
+}
+
+async function maybeCreateRedundantSubscriptionNotification(userId, email) {
+  const subscription =
+    await SubscriptionLocator.promises.getUserIndividualSubscription(userId)
+  if (!subscription || subscription.groupPlan) {
+    return
+  }
+
+  const affiliations = await InstitutionsAPI.promises.getUserAffiliations(
+    userId
+  )
+  const confirmedAffiliation = affiliations.find(a => a.email === email)
+  if (confirmedAffiliation.licence === 'free') {
+    return
+  }
+
+  await NotificationsBuilder.promises
+    .redundantPersonalSubscription(
+      {
+        institutionId: confirmedAffiliation.institution.id,
+        institutionName: confirmedAffiliation.institution.name,
+      },
+      { _id: userId }
+    )
+    .create()
 }
 
 async function removeEmailAddress(userId, email, skipParseEmail = false) {

+ 0 - 71
services/web/test/unit/src/User/SAMLIdentityManagerTests.js

@@ -545,77 +545,6 @@ describe('SAMLIdentityManager', function () {
     })
   })
 
-  describe('redundantSubscription', function () {
-    const userId = '1bv'
-    const providerId = 123
-    const providerName = 'University Name'
-
-    describe('with a personal subscription', function () {
-      beforeEach(function () {
-        this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
-          {
-            planCode: 'professional',
-          }
-        )
-      })
-
-      it('should create redundant personal subscription notification ', async function () {
-        try {
-          await this.SAMLIdentityManager.redundantSubscription(
-            userId,
-            providerId,
-            providerName
-          )
-        } catch (error) {
-          expect(error).to.not.exist
-        }
-        expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
-          .to.have.been.calledOnce
-      })
-    })
-
-    describe('with a group subscription', function () {
-      beforeEach(function () {
-        this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
-          {
-            planCode: 'professional',
-            groupPlan: true,
-          }
-        )
-      })
-
-      it('should create redundant personal subscription notification ', async function () {
-        try {
-          await this.SAMLIdentityManager.redundantSubscription(
-            userId,
-            providerId,
-            providerName
-          )
-        } catch (error) {
-          expect(error).to.not.exist
-        }
-        expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
-          .to.not.have.been.called
-      })
-    })
-
-    describe('without a personal subscription', function () {
-      it('should not create redundant personal subscription notification ', async function () {
-        try {
-          await this.SAMLIdentityManager.redundantSubscription(
-            userId,
-            providerId,
-            providerName
-          )
-        } catch (error) {
-          expect(error).to.not.exist
-        }
-        expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
-          .to.not.have.been.called
-      })
-    })
-  })
-
   describe('migrateIdentifier', function () {
     const userId = '5efb8b6e9b647b0027e4c0b0'
     const externalUserId = '987zyx'

+ 63 - 0
services/web/test/unit/src/User/UserUpdaterTests.js

@@ -73,6 +73,7 @@ describe('UserUpdater', function () {
       promises: {
         addAffiliation: sinon.stub().resolves(),
         removeAffiliation: sinon.stub().resolves(),
+        getUserAffiliations: sinon.stub().resolves(),
       },
     }
     this.EmailHandler = {
@@ -94,6 +95,20 @@ describe('UserUpdater', function () {
       },
     }
 
+    this.SubscriptionLocator = {
+      promises: {
+        getUserIndividualSubscription: sinon.stub().resolves(),
+      },
+    }
+
+    this.NotificationsBuilder = {
+      promises: {
+        redundantPersonalSubscription: sinon
+          .stub()
+          .returns({ create: () => {} }),
+      },
+    }
+
     this.UserUpdater = SandboxedModule.require(MODULE_PATH, {
       requires: {
         '../Helpers/Mongo': { normalizeQuery },
@@ -109,6 +124,8 @@ describe('UserUpdater', function () {
         './UserAuditLogHandler': this.UserAuditLogHandler,
         '../Analytics/AnalyticsManager': this.AnalyticsManager,
         '../../Errors/Errors': Errors,
+        '../Subscription/SubscriptionLocator': this.SubscriptionLocator,
+        '../Notifications/NotificationsBuilder': this.NotificationsBuilder,
       },
     })
 
@@ -910,5 +927,51 @@ describe('UserUpdater', function () {
         this.user._id
       )
     })
+
+    describe('with institution licence and subscription', function () {
+      beforeEach(async function () {
+        this.affiliation = {
+          email: this.newEmail,
+          licence: 'pro_plus',
+          institution: {
+            id: 123,
+            name: 'Institution',
+          },
+        }
+        this.InstitutionsAPI.promises.getUserAffiliations.resolves([
+          this.affiliation,
+          { email: 'other@email.edu' },
+        ])
+        this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
+          {
+            planCode: 'personal',
+            groupPlan: false,
+          }
+        )
+      })
+
+      it('creates redundant subscription notification', async function () {
+        await this.UserUpdater.promises.confirmEmail(
+          this.user._id,
+          this.newEmail
+        )
+        sinon.assert.calledWith(
+          this.InstitutionsAPI.promises.getUserAffiliations,
+          this.user._id
+        )
+        sinon.assert.calledWith(
+          this.SubscriptionLocator.promises.getUserIndividualSubscription,
+          this.user._id
+        )
+        sinon.assert.calledWith(
+          this.NotificationsBuilder.promises.redundantPersonalSubscription,
+          {
+            institutionId: 123,
+            institutionName: 'Institution',
+          },
+          { _id: this.user._id }
+        )
+      })
+    })
   })
 })