Просмотр исходного кода

Merge pull request #27017 from overleaf/ii-flexible-licensing-manually-billed-users-add-seats-revert

[web] Revert "FL manually billed subscriptions with no upsell"

GitOrigin-RevId: 3ce5441f825f9a364d3c17e3ac09aa3b9e87b0e7
ilkin-overleaf 1 год назад
Родитель
Сommit
f5dd356df3

+ 3 - 0
services/web/app/src/Features/Subscription/Errors.js

@@ -26,6 +26,8 @@ class SubtotalLimitExceededError extends OError {}
 
 
 class HasPastDueInvoiceError extends OError {}
 class HasPastDueInvoiceError extends OError {}
 
 
+class HasNoAdditionalLicenseWhenManuallyCollectedError extends OError {}
+
 class PaymentActionRequiredError extends OError {
 class PaymentActionRequiredError extends OError {
   constructor(info) {
   constructor(info) {
     super('Payment action required', info)
     super('Payment action required', info)
@@ -43,4 +45,5 @@ module.exports = {
   InactiveError,
   InactiveError,
   SubtotalLimitExceededError,
   SubtotalLimitExceededError,
   HasPastDueInvoiceError,
   HasPastDueInvoiceError,
+  HasNoAdditionalLicenseWhenManuallyCollectedError,
 }
 }

+ 12 - 3
services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs

@@ -19,6 +19,7 @@ import {
   InactiveError,
   InactiveError,
   SubtotalLimitExceededError,
   SubtotalLimitExceededError,
   HasPastDueInvoiceError,
   HasPastDueInvoiceError,
+  HasNoAdditionalLicenseWhenManuallyCollectedError,
 } from './Errors.js'
 } from './Errors.js'
 
 
 /**
 /**
@@ -164,6 +165,9 @@ async function addSeatsToGroupSubscription(req, res) {
         paymentProviderSubscription,
         paymentProviderSubscription,
         userId
         userId
       )
       )
+      await SubscriptionGroupHandler.promises.ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual(
+        paymentProviderSubscription
+      )
     } else {
     } else {
       await SubscriptionGroupHandler.promises.ensureSubscriptionCollectionMethodIsNotManual(
       await SubscriptionGroupHandler.promises.ensureSubscriptionCollectionMethodIsNotManual(
         paymentProviderSubscription
         paymentProviderSubscription
@@ -187,7 +191,10 @@ async function addSeatsToGroupSubscription(req, res) {
       )
       )
     }
     }
 
 
-    if (error instanceof ManuallyCollectedError) {
+    if (
+      error instanceof ManuallyCollectedError ||
+      error instanceof HasNoAdditionalLicenseWhenManuallyCollectedError
+    ) {
       return res.redirect(
       return res.redirect(
         '/user/subscription/group/manually-collected-subscription'
         '/user/subscription/group/manually-collected-subscription'
       )
       )
@@ -231,7 +238,8 @@ async function previewAddSeatsSubscriptionChange(req, res) {
       error instanceof ManuallyCollectedError ||
       error instanceof ManuallyCollectedError ||
       error instanceof PendingChangeError ||
       error instanceof PendingChangeError ||
       error instanceof InactiveError ||
       error instanceof InactiveError ||
-      error instanceof HasPastDueInvoiceError
+      error instanceof HasPastDueInvoiceError ||
+      error instanceof HasNoAdditionalLicenseWhenManuallyCollectedError
     ) {
     ) {
       return res.status(422).end()
       return res.status(422).end()
     }
     }
@@ -274,7 +282,8 @@ async function createAddSeatsSubscriptionChange(req, res) {
       error instanceof ManuallyCollectedError ||
       error instanceof ManuallyCollectedError ||
       error instanceof PendingChangeError ||
       error instanceof PendingChangeError ||
       error instanceof InactiveError ||
       error instanceof InactiveError ||
-      error instanceof HasPastDueInvoiceError
+      error instanceof HasPastDueInvoiceError ||
+      error instanceof HasNoAdditionalLicenseWhenManuallyCollectedError
     ) {
     ) {
       return res.status(422).end()
       return res.status(422).end()
     }
     }

+ 22 - 0
services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js

@@ -18,6 +18,7 @@ const {
   PendingChangeError,
   PendingChangeError,
   InactiveError,
   InactiveError,
   HasPastDueInvoiceError,
   HasPastDueInvoiceError,
+  HasNoAdditionalLicenseWhenManuallyCollectedError,
 } = require('./Errors')
 } = require('./Errors')
 const EmailHelper = require('../Helpers/EmailHelper')
 const EmailHelper = require('../Helpers/EmailHelper')
 const { InvalidEmailError } = require('../Errors/Errors')
 const { InvalidEmailError } = require('../Errors/Errors')
@@ -123,6 +124,22 @@ async function ensureSubscriptionHasNoPastDueInvoice(subscription) {
   }
   }
 }
 }
 
 
+async function ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual(
+  paymentProviderSubscription
+) {
+  if (
+    paymentProviderSubscription.isCollectionMethodManual &&
+    !paymentProviderSubscription.hasAddOn(MEMBERS_LIMIT_ADD_ON_CODE)
+  ) {
+    throw new HasNoAdditionalLicenseWhenManuallyCollectedError(
+      'This subscription is being collected manually has no "additional-license" add-on',
+      {
+        subscription_id: paymentProviderSubscription.id,
+      }
+    )
+  }
+}
+
 async function getUsersGroupSubscriptionDetails(userId) {
 async function getUsersGroupSubscriptionDetails(userId) {
   const subscription =
   const subscription =
     await SubscriptionLocator.promises.getUsersSubscription(userId)
     await SubscriptionLocator.promises.getUsersSubscription(userId)
@@ -470,6 +487,10 @@ module.exports = {
   ensureSubscriptionHasNoPastDueInvoice: callbackify(
   ensureSubscriptionHasNoPastDueInvoice: callbackify(
     ensureSubscriptionHasNoPastDueInvoice
     ensureSubscriptionHasNoPastDueInvoice
   ),
   ),
+  ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual:
+    callbackify(
+      ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual
+    ),
   getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup),
   getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup),
   isUserPartOfGroup: callbackify(isUserPartOfGroup),
   isUserPartOfGroup: callbackify(isUserPartOfGroup),
   getGroupPlanUpgradePreview: callbackify(getGroupPlanUpgradePreview),
   getGroupPlanUpgradePreview: callbackify(getGroupPlanUpgradePreview),
@@ -484,6 +505,7 @@ module.exports = {
     ensureSubscriptionCollectionMethodIsNotManual,
     ensureSubscriptionCollectionMethodIsNotManual,
     ensureSubscriptionHasNoPendingChanges,
     ensureSubscriptionHasNoPendingChanges,
     ensureSubscriptionHasNoPastDueInvoice,
     ensureSubscriptionHasNoPastDueInvoice,
+    ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual,
     getTotalConfirmedUsersInGroup,
     getTotalConfirmedUsersInGroup,
     isUserPartOfGroup,
     isUserPartOfGroup,
     getUsersGroupSubscriptionDetails,
     getUsersGroupSubscriptionDetails,

+ 1 - 1
services/web/frontend/extracted-translations.json

@@ -874,7 +874,7 @@
   "issued_on": "",
   "issued_on": "",
   "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "",
   "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "",
   "it_looks_like_your_account_is_billed_manually": "",
   "it_looks_like_your_account_is_billed_manually": "",
-  "it_looks_like_your_account_is_billed_manually_upgrading_subscription": "",
+  "it_looks_like_your_account_is_billed_manually_purchasing_additional_license_or_upgrading_subscription": "",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "",
   "italics": "",
   "italics": "",
   "join_beta_program": "",
   "join_beta_program": "",

+ 1 - 1
services/web/frontend/js/features/group-management/components/manually-collected-subscription.tsx

@@ -24,7 +24,7 @@ function ManuallyCollectedSubscription() {
         content={
         content={
           isFlexibleGroupLicensingForManuallyBilledSubscriptions ? (
           isFlexibleGroupLicensingForManuallyBilledSubscriptions ? (
             <Trans
             <Trans
-              i18nKey="it_looks_like_your_account_is_billed_manually_upgrading_subscription"
+              i18nKey="it_looks_like_your_account_is_billed_manually_purchasing_additional_license_or_upgrading_subscription"
               components={[
               components={[
                 // eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
                 // eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
                 <a href="/contact" rel="noreferrer noopener" />,
                 <a href="/contact" rel="noreferrer noopener" />,

+ 1 - 1
services/web/locales/en.json

@@ -1133,7 +1133,7 @@
   "it": "Italian",
   "it": "Italian",
   "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "It looks like that didn’t work. You can try again or <0>get in touch</0> with our Support team for more help.",
   "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "It looks like that didn’t work. You can try again or <0>get in touch</0> with our Support team for more help.",
   "it_looks_like_your_account_is_billed_manually": "It looks like your account is being billed manually - adding seats or upgrading your subscription can only be done by the Support team. Please <0>get in touch</0> for help.",
   "it_looks_like_your_account_is_billed_manually": "It looks like your account is being billed manually - adding seats or upgrading your subscription can only be done by the Support team. Please <0>get in touch</0> for help.",
-  "it_looks_like_your_account_is_billed_manually_upgrading_subscription": "It looks like your account is being billed manually - upgrading your subscription can only be done by the Support team. Please <0>get in touch</0> for help.",
+  "it_looks_like_your_account_is_billed_manually_purchasing_additional_license_or_upgrading_subscription": "It looks like your account is being billed manually - purchasing additional licenses or upgrading your subscription can only be done by the Support team. Please <0>get in touch</0> for help.",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "It looks like your payment details are missing. Please <0>update your billing information</0>, or <1>get in touch</1> with our Support team for more help.",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "It looks like your payment details are missing. Please <0>update your billing information</0>, or <1>get in touch</1> with our Support team for more help.",
   "italics": "Italics",
   "italics": "Italics",
   "ja": "Japanese",
   "ja": "Japanese",

+ 25 - 0
services/web/test/unit/src/Subscription/SubscriptionGroupController.test.mjs

@@ -73,6 +73,8 @@ describe('SubscriptionGroupController', function () {
           .resolves(ctx.previewSubscriptionChangeData),
           .resolves(ctx.previewSubscriptionChangeData),
         checkBillingInfoExistence: sinon.stub().resolves(ctx.paymentMethod),
         checkBillingInfoExistence: sinon.stub().resolves(ctx.paymentMethod),
         updateSubscriptionPaymentTerms: sinon.stub().resolves(),
         updateSubscriptionPaymentTerms: sinon.stub().resolves(),
+        ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual:
+          sinon.stub().resolves(),
       },
       },
     }
     }
 
 
@@ -140,6 +142,7 @@ describe('SubscriptionGroupController', function () {
       InactiveError: class extends Error {},
       InactiveError: class extends Error {},
       SubtotalLimitExceededError: class extends Error {},
       SubtotalLimitExceededError: class extends Error {},
       HasPastDueInvoiceError: class extends Error {},
       HasPastDueInvoiceError: class extends Error {},
+      HasNoAdditionalLicenseWhenManuallyCollectedError: class extends Error {},
     }
     }
 
 
     vi.doMock(
     vi.doMock(
@@ -521,6 +524,28 @@ describe('SubscriptionGroupController', function () {
       })
       })
     })
     })
 
 
+    it('should redirect to manually collected subscription error page when collection method is manual and has no additional license add-on', async function (ctx) {
+      await new Promise(resolve => {
+        ctx.SubscriptionGroupHandler.promises.ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual =
+          sinon
+            .stub()
+            .throws(
+              new ctx.Errors.HasNoAdditionalLicenseWhenManuallyCollectedError()
+            )
+
+        const res = {
+          redirect: url => {
+            url.should.equal(
+              '/user/subscription/group/manually-collected-subscription'
+            )
+            resolve()
+          },
+        }
+
+        ctx.Controller.addSeatsToGroupSubscription(ctx.req, res)
+      })
+    })
+
     it('should redirect to subscription page when there is a pending change', async function (ctx) {
     it('should redirect to subscription page when there is a pending change', async function (ctx) {
       await new Promise(resolve => {
       await new Promise(resolve => {
         ctx.SubscriptionGroupHandler.promises.ensureSubscriptionHasNoPendingChanges =
         ctx.SubscriptionGroupHandler.promises.ensureSubscriptionHasNoPendingChanges =

+ 43 - 0
services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js

@@ -806,6 +806,49 @@ describe('SubscriptionGroupHandler', function () {
     })
     })
   })
   })
 
 
+  describe('ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual', function () {
+    it('should throw if the subscription is manually collected and has no additional license add-on', async function () {
+      await expect(
+        this.Handler.promises.ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual(
+          {
+            isCollectionMethodManual: true,
+            hasAddOn: sinon
+              .stub()
+              .withArgs('additional-license')
+              .returns(false),
+          }
+        )
+      ).to.be.rejectedWith(
+        'This subscription is being collected manually has no "additional-license" add-on'
+      )
+    })
+
+    it('should not throw if the subscription is not manually collected and has no additional license add-on and ', async function () {
+      await expect(
+        this.Handler.promises.ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual(
+          {
+            isCollectionMethodManual: false,
+            hasAddOn: sinon
+              .stub()
+              .withArgs('additional-license')
+              .returns(false),
+          }
+        )
+      ).to.not.be.rejected
+    })
+
+    it('should not throw if the subscription is not manually collected and has additional license add-on', async function () {
+      await expect(
+        this.Handler.promises.ensureSubscriptionHasAdditionalLicenseAddOnWhenCollectionMethodIsManual(
+          {
+            isCollectionMethodManual: true,
+            hasAddOn: sinon.stub().withArgs('additional-license').returns(true),
+          }
+        )
+      ).to.not.be.rejected
+    })
+  })
+
   describe('getGroupPlanUpgradePreview', function () {
   describe('getGroupPlanUpgradePreview', function () {
     it('should generate preview for subscription upgrade', async function () {
     it('should generate preview for subscription upgrade', async function () {
       const result = await this.Handler.promises.getGroupPlanUpgradePreview(
       const result = await this.Handler.promises.getGroupPlanUpgradePreview(