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

feat: removing recurly revert logic since we are sunsetting recurly (#31470)

GitOrigin-RevId: c165c7d14ec5d57fdbe3b0962bd65222182a9f48
Jimmy Domagala-Tang 5 месяцев назад
Родитель
Сommit
793506c10f

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

@@ -47,8 +47,6 @@ class DuplicateNameError extends OError {}
 
 class InvalidNameError extends BackwardCompatibleError {}
 
-class IndeterminateInvoiceError extends OError {}
-
 class UnsupportedFileTypeError extends BackwardCompatibleError {}
 
 class FileTooLargeError extends BackwardCompatibleError {}
@@ -378,7 +376,6 @@ module.exports = {
   UnconfirmedEmailError,
   EmailExistsError,
   InvalidError,
-  IndeterminateInvoiceError,
   NotInV2Error,
   OutputFileFetchFailedError,
   SAMLAssertionAudienceMismatch,

+ 0 - 37
services/web/app/src/Features/Subscription/PaymentProviderEntities.mjs

@@ -394,43 +394,6 @@ export class PaymentProviderSubscription {
     })
   }
 
-  /**
-   * Form a request to revert the plan to it's last saved backup state
-   *
-   * @param {string} previousPlanCode
-   * @param {Array<AddOn> | null} previousAddOns
-   * @return {PaymentProviderSubscriptionChangeRequest}
-   *
-   * @throws {OError} if the restore point plan doesnt exist
-   */
-  getRequestForPlanRevert(previousPlanCode, previousAddOns) {
-    const lastSuccessfulPlan =
-      PlansLocator.findLocalPlanInSettings(previousPlanCode)
-    if (lastSuccessfulPlan == null) {
-      throw new OError('Unable to find plan in settings', { previousPlanCode })
-    }
-    const changeRequest = new PaymentProviderSubscriptionChangeRequest({
-      subscription: this,
-      timeframe: 'now',
-      planCode: previousPlanCode,
-    })
-
-    // defaulting to empty array is important, as that will wipe away any add-ons that were added in the failed payment
-    //  but were not part of the last successful subscription
-    const addOns = []
-    for (const previousAddon of previousAddOns || []) {
-      const addOnUpdate = new PaymentProviderSubscriptionAddOnUpdate({
-        code: previousAddon.addOnCode,
-        quantity: previousAddon.quantity,
-        unitPrice: previousAddon.unitAmountInCents / 100,
-      })
-      addOns.push(addOnUpdate)
-    }
-    changeRequest.addOnUpdates = addOns
-
-    return changeRequest
-  }
-
   /**
    * Upgrade group plan with the plan code provided
    *

+ 0 - 36
services/web/app/src/Features/Subscription/RecurlyClient.mjs

@@ -884,38 +884,6 @@ function subscriptionUpdateRequestToApi(updateRequest) {
   return requestBody
 }
 
-/**
- * Retrieves a list of failed invoices for a given Recurly subscription ID.
- *
- * @async
- * @function
- * @param {string} subscriptionId - The ID of the Recurly subscription to fetch failed invoices for.
- * @returns {Promise<Array<recurly.Invoice>>} A promise that resolves to an array of failed invoice objects.
- */
-async function getPastDueInvoices(subscriptionId) {
-  const failed = []
-  const invoices = client.listSubscriptionInvoices(`uuid-${subscriptionId}`, {
-    params: { state: 'past_due' },
-  })
-
-  for await (const invoice of invoices.each()) {
-    failed.push(invoice)
-  }
-  return failed
-}
-
-/**
- * Marks an invoice as failed using the Recurly client.
- *
- * @async
- * @function failInvoice
- * @param {string} invoiceId - The ID of the invoice to be marked as failed.
- * @returns {Promise<void>} Resolves when the invoice has been successfully marked as failed.
- */
-async function failInvoice(invoiceId) {
-  await client.markInvoiceFailed(invoiceId)
-}
-
 async function terminateSubscriptionByUuid(subscriptionUuid) {
   const subscription = await client.terminateSubscription(
     'uuid-' + subscriptionUuid,
@@ -952,8 +920,6 @@ export default {
   subscriptionIsCanceledOrExpired,
   pauseSubscriptionByUuid: callbackify(pauseSubscriptionByUuid),
   resumeSubscriptionByUuid: callbackify(resumeSubscriptionByUuid),
-  getPastDueInvoices: callbackify(getPastDueInvoices),
-  failInvoice: callbackify(failInvoice),
   terminateSubscriptionByUuid: callbackify(terminateSubscriptionByUuid),
 
   promises: {
@@ -975,8 +941,6 @@ export default {
     getPaymentMethod,
     getAddOn,
     getPlan,
-    getPastDueInvoices,
-    failInvoice,
     terminateSubscriptionByUuid,
   },
 }

+ 1 - 49
services/web/app/src/Features/Subscription/SubscriptionController.mjs

@@ -33,7 +33,6 @@ import UserGetter from '../User/UserGetter.mjs'
 import PermissionsManager from '../Authorization/PermissionsManager.mjs'
 import { sanitizeSessionUserForFrontEnd } from '../../infrastructure/FrontEndUser.mjs'
 import { z, parseReq } from '../../infrastructure/Validation.mjs'
-import { IndeterminateInvoiceError } from '../Errors/Errors.js'
 import SubscriptionLocator from './SubscriptionLocator.mjs'
 import { PaymentProviderSubscriptionChange } from './PaymentProviderEntities.mjs'
 
@@ -887,54 +886,7 @@ function recurlyCallback(req, res, next) {
     )
   )
 
-  // this is a recurly only case which is required since Recurly does not have a reliable way to check credit info pre-upgrade purchase
-  if (event === 'failed_payment_notification') {
-    if (!Settings.planReverts?.enabled) {
-      return res.sendStatus(200)
-    }
-
-    // A manual charge may have no subscription, in which case we get a
-    // <subscription_id nil="true"/> element, which produces an object instead
-    // of a string subscription_id.
-    const subscriptionId = eventData.transaction?.subscription_id
-    if (!subscriptionId || typeof subscriptionId !== 'string') {
-      logger.info(
-        { transactionId: eventData.transaction?.id },
-        'ignoring failed_payment_notification without subscription_id'
-      )
-      return res.sendStatus(200)
-    }
-
-    SubscriptionHandler.getSubscriptionRestorePoint(
-      subscriptionId,
-      function (err, lastSubscription) {
-        if (err) {
-          return next(err)
-        }
-        // if theres no restore point it could be a failed renewal, or no restore set. Either way it will be handled through dunning automatically
-        if (!lastSubscription || !lastSubscription?.planCode) {
-          return res.sendStatus(200)
-        }
-        SubscriptionHandler.revertPlanChange(
-          eventData.transaction.subscription_id,
-          lastSubscription,
-          function (err) {
-            if (err instanceof IndeterminateInvoiceError) {
-              logger.warn(
-                { recurlySubscriptionId: err.info.recurlySubscriptionId },
-                'could not determine invoice to fail for subscription'
-              )
-              return res.sendStatus(200)
-            }
-            if (err) {
-              return next(err)
-            }
-            return res.sendStatus(200)
-          }
-        )
-      }
-    )
-  } else if (
+  if (
     [
       'new_subscription_notification',
       'updated_subscription_notification',

+ 0 - 83
services/web/app/src/Features/Subscription/SubscriptionHandler.mjs

@@ -2,17 +2,14 @@
 
 import RecurlyWrapper from './RecurlyWrapper.mjs'
 
-import RecurlyClient from './RecurlyClient.mjs'
 import { User } from '../../models/User.mjs'
 import logger from '@overleaf/logger'
 import SubscriptionHelper from './SubscriptionHelper.mjs'
 import SubscriptionUpdater from './SubscriptionUpdater.mjs'
-import SubscriptionLocator from './SubscriptionLocator.mjs'
 import LimitationsManager from './LimitationsManager.mjs'
 import EmailHandler from '../Email/EmailHandler.mjs'
 import { callbackify } from '@overleaf/promise-utils'
 import UserUpdater from '../User/UserUpdater.mjs'
-import { IndeterminateInvoiceError } from '../Errors/Errors.js'
 import Modules from '../../infrastructure/Modules.mjs'
 import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
 import { AI_ADD_ON_CODE } from './AiHelper.mjs'
@@ -399,80 +396,6 @@ async function resumeSubscription(user) {
   await Modules.promises.hooks.fire('resumePaidSubscription', subscription)
 }
 
-/**
- * @param recurlySubscriptionId
- */
-async function getSubscriptionRestorePoint(recurlySubscriptionId) {
-  const lastSubscription =
-    await SubscriptionLocator.promises.getLastSuccessfulSubscription(
-      recurlySubscriptionId
-    )
-  return lastSubscription
-}
-
-/**
- * @param recurlySubscriptionId
- * @param subscriptionRestorePoint
- */
-async function revertPlanChange(
-  recurlySubscriptionId,
-  subscriptionRestorePoint
-) {
-  const subscription = await RecurlyClient.promises.getSubscription(
-    recurlySubscriptionId
-  )
-
-  const changeRequest = subscription.getRequestForPlanRevert(
-    subscriptionRestorePoint.planCode,
-    subscriptionRestorePoint.addOns
-  )
-
-  const pastDue = await RecurlyClient.promises.getPastDueInvoices(
-    recurlySubscriptionId
-  )
-
-  // only process revert requests within the past 24 hours, as we dont want to restore plans at the end of their dunning cycle
-  const yesterday = new Date()
-  yesterday.setDate(yesterday.getDate() - 1)
-  if (
-    pastDue.length !== 1 ||
-    !pastDue[0].id ||
-    !pastDue[0].dueAt ||
-    pastDue[0].dueAt < yesterday ||
-    pastDue[0].collectionMethod !== 'automatic'
-  ) {
-    throw new IndeterminateInvoiceError(
-      'cant determine invoice to fail for plan revert',
-      {
-        recurlySubscriptionId,
-      }
-    )
-  }
-
-  await RecurlyClient.promises.failInvoice(pastDue[0].id)
-  await SubscriptionUpdater.promises.setSubscriptionWasReverted(
-    subscriptionRestorePoint._id
-  )
-  await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest)
-  await syncSubscription({ uuid: recurlySubscriptionId }, {})
-}
-
-async function setSubscriptionRestorePoint(userId) {
-  const subscription =
-    await SubscriptionLocator.promises.getUsersSubscription(userId)
-  // if the subscription is not a recurly one, we can return early as we dont allow for failed payments on other payment providers
-  //  we need to deal with it for recurly, because we cant verify payment in advance
-  if (!subscription?.recurlySubscription_id || !subscription.planCode) {
-    return
-  }
-  await SubscriptionUpdater.promises.setRestorePoint(
-    subscription.id,
-    subscription.planCode,
-    subscription.addOns,
-    false
-  )
-}
-
 export default {
   validateNoSubscriptionInRecurly: callbackify(validateNoSubscriptionInRecurly),
   createSubscription: callbackify(createSubscription),
@@ -490,9 +413,6 @@ export default {
   reactivateAddon: callbackify(reactivateAddon),
   pauseSubscription: callbackify(pauseSubscription),
   resumeSubscription: callbackify(resumeSubscription),
-  revertPlanChange: callbackify(revertPlanChange),
-  setSubscriptionRestorePoint: callbackify(setSubscriptionRestorePoint),
-  getSubscriptionRestorePoint: callbackify(getSubscriptionRestorePoint),
   promises: {
     validateNoSubscriptionInRecurly,
     createSubscription,
@@ -510,8 +430,5 @@ export default {
     reactivateAddon,
     pauseSubscription,
     resumeSubscription,
-    revertPlanChange,
-    setSubscriptionRestorePoint,
-    getSubscriptionRestorePoint,
   },
 }

+ 0 - 25
services/web/app/src/Features/Subscription/SubscriptionLocator.mjs

@@ -1,7 +1,3 @@
-/**
- * @import { AddOn } from '../../../../types/subscription/plan'
- */
-
 import { callbackifyAll } from '@overleaf/promise-utils'
 
 import { Subscription } from '../../models/Subscription.mjs'
@@ -183,27 +179,6 @@ const SubscriptionLocator = {
     }
   },
 
-  /**
-   * Retrieves the last successful subscription for a given user.
-   *
-   * @async
-   * @function
-   * @param {string} recurlyId - The ID of the recurly subscription tied to the mongo subscription to check for a previous successful state.
-   * @returns {Promise<{_id: ObjectId, planCode: string, addOns: [AddOn]}|null>} A promise that resolves to the last successful planCode and addon state,
-   *   or null if we havent stored a previous
-   */
-  async getLastSuccessfulSubscription(recurlyId) {
-    const subscription = await Subscription.findOne({
-      recurlySubscription_id: recurlyId,
-    }).exec()
-    return subscription && subscription.lastSuccesfulSubscription
-      ? {
-          ...subscription.lastSuccesfulSubscription,
-          _id: subscription._id,
-        }
-      : null
-  },
-
   async getUserSubscriptionStatus(userId) {
     let usersSubscription = { personal: false, group: false }
 

+ 0 - 54
services/web/app/src/Features/Subscription/SubscriptionUpdater.mjs

@@ -20,7 +20,6 @@ import Modules from '../../infrastructure/Modules.mjs'
  * @typedef {import('../../../../types/subscription/dashboard/subscription').Subscription} Subscription
  * @typedef {import('../../../../types/subscription/dashboard/subscription').PaymentProvider} PaymentProvider
  * @typedef {import('../../../../types/group-management/group-audit-log').GroupAuditLog} GroupAuditLog
- * @import { AddOn } from '../../../../types/subscription/plan'
  * @typedef {InstanceType<Subscription>} MongoSubscription
  */
 
@@ -518,28 +517,6 @@ async function _sendSubscriptionEventForAllMembers(subscriptionId, event) {
   }
 }
 
-/**
- * Sets the plan code and addon state to revert the plan to in case of failed upgrades, or clears the last restore point if it was used/ voided
- * @param {ObjectId} subscriptionId the mongo ID of the subscription to set the restore point for
- * @param {string} planCode the plan code to revert to
- * @param {Array<AddOn>} addOns the addOns to revert to
- * @param {Boolean} consumed whether the restore point was used to revert a subscription
- */
-async function setRestorePoint(subscriptionId, planCode, addOns, consumed) {
-  const update = {
-    $set: {
-      'lastSuccesfulSubscription.planCode': planCode,
-      'lastSuccesfulSubscription.addOns': addOns,
-    },
-  }
-
-  if (consumed) {
-    update.$inc = { timesRevertedDueToFailedPayment: 1 }
-  }
-
-  await Subscription.updateOne({ _id: subscriptionId }, update).exec()
-}
-
 /**
  * Change the ownershiop of the given subscription.
  * @param {MongoSubscription} subscription
@@ -572,31 +549,6 @@ async function transferSubscriptionOwnership(
   await Subscription.updateOne(query, update).exec()
 }
 
-/**
- * Clears the restore point for a given subscription, and signals that the subscription was sucessfully reverted.
- *
- * @async
- * @function setSubscriptionWasReverted
- * @param {ObjectId} subscriptionId the mongo ID of the subscription to set the restore point for
- * @returns {Promise<void>} Resolves when the restore point has been cleared.
- */
-async function setSubscriptionWasReverted(subscriptionId) {
-  // consume the backup and flag that the subscription was reverted due to failed payment
-  await setRestorePoint(subscriptionId, null, null, true)
-}
-
-/**
- * Clears the restore point for a given subscription, and signals that the subscription was not reverted.
- *
- * @async
- * @function voidRestorePoint
- * @param {string} subscriptionId - The unique identifier of the subscription.
- * @returns {Promise<void>} Resolves when the restore point has been cleared.
- */
-async function voidRestorePoint(subscriptionId) {
-  await setRestorePoint(subscriptionId, null, null, false)
-}
-
 export default {
   updateAdmin: callbackify(updateAdmin),
   syncSubscription: callbackify(syncSubscription),
@@ -611,9 +563,6 @@ export default {
   restoreSubscription: callbackify(restoreSubscription),
   updateSubscriptionFromRecurly: callbackify(updateSubscriptionFromRecurly),
   scheduleRefreshFeatures: callbackify(scheduleRefreshFeatures),
-  setSubscriptionRestorePoint: callbackify(setRestorePoint),
-  setSubscriptionWasReverted: callbackify(setSubscriptionWasReverted),
-  voidRestorePoint: callbackify(voidRestorePoint),
   promises: {
     updateAdmin,
     syncSubscription,
@@ -628,9 +577,6 @@ export default {
     restoreSubscription,
     updateSubscriptionFromRecurly,
     scheduleRefreshFeatures,
-    setRestorePoint,
-    setSubscriptionWasReverted,
-    voidRestorePoint,
     handleExpiredSubscription,
     transferSubscriptionOwnership,
   },

+ 0 - 7
services/web/app/src/models/Subscription.mjs

@@ -50,13 +50,6 @@ export const SubscriptionSchema = new Schema(
     invited_emails: [String],
     teamInvites: [TeamInviteSchema],
     recurlySubscription_id: String,
-    lastSuccesfulSubscription: {
-      planCode: {
-        type: String,
-      },
-      addOns: Schema.Types.Mixed,
-    },
-    timesRevertedDueToFailedPayment: { type: Number, default: 0 },
     teamName: { type: String },
     teamNotice: { type: String },
     planCode: { type: String },

+ 0 - 69
services/web/test/unit/src/Subscription/PaymentProviderEntities.test.mjs

@@ -859,75 +859,6 @@ describe('PaymentProviderEntities', function () {
             ).to.throw(Errors.AddOnNotPresentError)
           })
         })
-
-        describe('getRequestForPlanRevert()', function () {
-          beforeEach(function (ctx) {
-            const { PaymentProviderSubscription } = ctx.PaymentProviderEntities
-            ctx.subscription = new PaymentProviderSubscription({
-              id: 'subscription-id',
-              userId: 'user-id',
-              planCode: 'regular-plan',
-              planName: 'My Plan',
-              planPrice: 10,
-              addOns: [
-                {
-                  addOnCode: 'addon-1',
-                  quantity: 2,
-                  unitAmountInCents: 500,
-                },
-                {
-                  addOnCode: 'addon-2',
-                  quantity: 1,
-                  unitAmountInCents: 600,
-                },
-              ],
-              subtotal: 10.99,
-              taxRate: 0.2,
-              taxAmount: 2.4,
-              total: 14.4,
-              currency: 'USD',
-            })
-          })
-
-          it('throws if the plan to revert to doesnt exist', function (ctx) {
-            const invalidPlanCode = 'non-existent-plan'
-            expect(() =>
-              ctx.subscription.getRequestForPlanRevert(invalidPlanCode, null)
-            ).to.throw('Unable to find plan in settings')
-          })
-
-          it('creates a change request with the restore point', function (ctx) {
-            const previousPlanCode = 'cheap-plan'
-            const previousAddOns = [
-              { addOnCode: 'addon-1', quantity: 1, unitAmountInCents: 500 },
-            ]
-            const changeRequest = ctx.subscription.getRequestForPlanRevert(
-              previousPlanCode,
-              previousAddOns
-            )
-            expect(changeRequest).to.be.an.instanceOf(
-              ctx.PaymentProviderEntities
-                .PaymentProviderSubscriptionChangeRequest
-            )
-            expect(changeRequest.planCode).to.equal(previousPlanCode)
-            expect(changeRequest.addOnUpdates).to.deep.equal([
-              {
-                code: 'addon-1',
-                quantity: 1,
-                unitPrice: 5,
-              },
-            ])
-          })
-
-          it('defaults to addons to an empty array to clear the addon state', function (ctx) {
-            const previousPlanCode = 'cheap-plan'
-            const changeRequest = ctx.subscription.getRequestForPlanRevert(
-              previousPlanCode,
-              null
-            )
-            expect(changeRequest.addOnUpdates).to.deep.equal([])
-          })
-        })
       })
     })
 

+ 0 - 47
services/web/test/unit/src/Subscription/RecurlyClient.test.mjs

@@ -979,51 +979,4 @@ describe('RecurlyClient', function () {
       )
     })
   })
-
-  describe('getPastDueInvoices', function () {
-    beforeEach(function (ctx) {
-      ctx.client.listSubscriptionInvoices = sinon.stub()
-    })
-
-    it('should return empty if no past due are found', async function (ctx) {
-      ctx.client.listSubscriptionInvoices.returns({
-        each: async function* () {},
-      })
-      const invoices = await ctx.RecurlyClient.promises.getPastDueInvoices(
-        ctx.subscription.id
-      )
-      expect(invoices).to.deep.equal([])
-    })
-
-    it('should return past due invoice', async function (ctx) {
-      const pastDueInvoice = { id: 'invoice-1', state: 'past_due' }
-      ctx.client.listSubscriptionInvoices.returns({
-        each: async function* () {
-          yield pastDueInvoice
-        },
-      })
-      const invoices = await ctx.RecurlyClient.promises.getPastDueInvoices(
-        ctx.subscription.id
-      )
-      expect(invoices).to.deep.equal([pastDueInvoice])
-    })
-
-    it('should return multiple invoices if multiple past due exist', async function (ctx) {
-      const pastDueInvoices = [
-        { id: 'invoice-1', state: 'past_due' },
-        { id: 'invoice-2', state: 'past_due' },
-      ]
-      ctx.client.listSubscriptionInvoices.returns({
-        each: async function* () {
-          for (const invoice of pastDueInvoices) {
-            yield invoice
-          }
-        },
-      })
-      const invoices = await ctx.RecurlyClient.promises.getPastDueInvoices(
-        ctx.subscription.id
-      )
-      expect(invoices).to.deep.equal(pastDueInvoices)
-    })
-  })
 })

+ 0 - 143
services/web/test/unit/src/Subscription/SubscriptionController.test.mjs

@@ -57,7 +57,6 @@ describe('SubscriptionController', function () {
       syncSubscription: sinon.stub().yields(),
       attemptPaypalInvoiceCollection: sinon.stub().yields(),
       startFreeTrial: sinon.stub(),
-      revertPlanChange: sinon.stub(),
       promises: {
         createSubscription: sinon.stub().resolves(),
         updateSubscription: sinon.stub().resolves(),
@@ -85,7 +84,6 @@ describe('SubscriptionController', function () {
           tax: 0,
           total: 2000,
         }),
-        revertPlanChange: sinon.stub().resolves(),
       },
     }
 
@@ -133,9 +131,6 @@ describe('SubscriptionController', function () {
           subdomain: 'sl',
         },
       },
-      planReverts: {
-        enabled: false,
-      },
       siteUrl: 'http://de.overleaf.dev:3000',
     }
     ctx.AuthorizationManager = {
@@ -869,144 +864,6 @@ describe('SubscriptionController', function () {
         ctx.res.sendStatus.calledWith(200)
       })
     })
-
-    describe('with a failed payment notification', function () {
-      describe('with planReverts disabled in settings', function () {
-        beforeEach(async function (ctx) {
-          await new Promise(resolve => {
-            ctx.settings.planReverts = { enabled: false }
-            ctx.SubscriptionHandler.revertPlanChange = sinon.stub()
-
-            ctx.req.body = {
-              failed_payment_notification: {
-                transaction: {
-                  subscription_id: 'subscription-123',
-                },
-              },
-            }
-
-            ctx.res = {
-              sendStatus() {
-                resolve()
-              },
-            }
-            sinon.spy(ctx.res, 'sendStatus')
-            ctx.SubscriptionController.recurlyCallback(ctx.req, ctx.res)
-          })
-        })
-        it('should not call revertPlanChange', function (ctx) {
-          expect(ctx.SubscriptionHandler.revertPlanChange.called).to.be.false
-        })
-
-        it('should respond with 200', async function (ctx) {
-          await new Promise(resolve => {
-            ctx.res.sendStatus.calledWith(200)
-            resolve()
-          })
-        })
-      })
-
-      describe('with planReverts enabled in settings', function () {
-        beforeEach(function (ctx) {
-          ctx.settings.planReverts = { enabled: true }
-        })
-
-        describe('with no valid restore point', function () {
-          beforeEach(async function (ctx) {
-            await new Promise(resolve => {
-              ctx.SubscriptionHandler.getSubscriptionRestorePoint = sinon
-                .stub()
-                .yields(null, null)
-              ctx.SubscriptionHandler.revertPlanChange = sinon.stub()
-
-              ctx.req.body = {
-                failed_payment_notification: {
-                  transaction: {
-                    subscription_id: 'subscription-123',
-                  },
-                },
-              }
-              ctx.res = {
-                sendStatus() {
-                  resolve()
-                },
-              }
-              sinon.spy(ctx.res, 'sendStatus')
-              ctx.SubscriptionController.recurlyCallback(ctx.req, ctx.res)
-            })
-          })
-          it('should not call revertPlanChange()', function (ctx) {
-            expect(ctx.SubscriptionHandler.revertPlanChange.called).to.be.false
-          })
-
-          it('should respond with 200', function (ctx) {
-            ctx.res.sendStatus.calledWith(200)
-          })
-        })
-
-        describe('with a valid restore point', function () {
-          beforeEach(async function (ctx) {
-            await new Promise(resolve => {
-              ctx.addOns = [
-                {
-                  addOnCode: 'addon-1',
-                  quantity: 2,
-                  unitAmountInCents: 500,
-                },
-                {
-                  addOnCode: 'addon-2',
-                  quantity: 1,
-                  unitAmountInCents: 600,
-                },
-              ]
-              ctx.lastSubscription = {
-                planCode: 'gold',
-                addOns: ctx.addOns,
-              }
-              ctx.SubscriptionHandler.getSubscriptionRestorePoint = sinon
-                .stub()
-                .yields(null, ctx.lastSubscription)
-              ctx.SubscriptionHandler.revertPlanChange = sinon.stub().yields()
-              ctx.req.body = {
-                failed_payment_notification: {
-                  transaction: {
-                    subscription_id: 'subscription-123',
-                  },
-                },
-              }
-              ctx.res = {
-                sendStatus() {
-                  resolve()
-                },
-              }
-              sinon.spy(ctx.res, 'sendStatus')
-              ctx.SubscriptionController.recurlyCallback(ctx.req, ctx.res)
-            })
-          })
-
-          it('should get the subscription restore point', function (ctx) {
-            expect(
-              ctx.SubscriptionHandler.getSubscriptionRestorePoint.calledWith(
-                'subscription-123'
-              )
-            ).to.be.true
-          })
-
-          it('should call revertPlanChange()', function (ctx) {
-            expect(
-              ctx.SubscriptionHandler.revertPlanChange.calledWith(
-                'subscription-123',
-                ctx.lastSubscription
-              )
-            ).to.be.true
-          })
-
-          it('should respond with 200', function (ctx) {
-            ctx.res.sendStatus.calledWith(200)
-          })
-        })
-      })
-    })
   })
 
   describe('purchaseAddon', function () {

+ 0 - 149
services/web/test/unit/src/Subscription/SubscriptionHandler.test.mjs

@@ -105,8 +105,6 @@ describe('SubscriptionHandler', function () {
           .resolves(ctx.activeRecurlyClientSubscription),
         pauseSubscriptionByUuid: sinon.stub().resolves(),
         resumeSubscriptionByUuid: sinon.stub().resolves(),
-        failInvoice: sinon.stub(),
-        getPastDueInvoices: sinon.stub(),
       },
     }
     ctx.SubscriptionUpdater = {
@@ -115,7 +113,6 @@ describe('SubscriptionHandler', function () {
         syncSubscription: sinon.stub().resolves(),
         syncStripeSubscription: sinon.stub().resolves(),
         startFreeTrial: sinon.stub().resolves(),
-        setSubscriptionWasReverted: sinon.stub().resolves(),
       },
     }
 
@@ -1004,150 +1001,4 @@ describe('SubscriptionHandler', function () {
       })
     })
   })
-
-  describe('revertPlanChange', function () {
-    describe('with correct invoices', function () {
-      beforeEach(async function (ctx) {
-        ctx.subscriptionRestorePoint = {
-          planCode: 'collaborator',
-          addOns: [
-            { addOnCode: 'addon-1', quantity: 1, unitAmountInCents: 500 },
-          ],
-          _id: 'restore-point-id',
-        }
-        ctx.pastDueInvoice = {
-          id: 'invoice-123',
-          dueAt: new Date(),
-          collectionMethod: 'automatic',
-        }
-        ctx.user.id = ctx.activeRecurlySubscription.account.account_code
-        ctx.User.findById = (userId, projection) => ({
-          exec: () => {
-            userId.should.equal(ctx.user.id)
-            return Promise.resolve(ctx.user)
-          },
-        })
-        ctx.RecurlyClient.promises.getSubscription.resolves(
-          ctx.activeRecurlyClientSubscription
-        )
-        ctx.RecurlyClient.promises.getPastDueInvoices.resolves([
-          ctx.pastDueInvoice,
-        ])
-        ctx.RecurlyClient.promises.failInvoice.resolves()
-        ctx.SubscriptionUpdater.promises.setSubscriptionWasReverted.resolves()
-        ctx.RecurlyClient.promises.applySubscriptionChangeRequest.resolves()
-
-        await ctx.SubscriptionHandler.promises.revertPlanChange(
-          ctx.activeRecurlyClientSubscription.id,
-          ctx.subscriptionRestorePoint
-        )
-      })
-
-      it('should fetch the subscription from recurly', async function (ctx) {
-        expect(
-          ctx.RecurlyClient.promises.getSubscription.calledWith(
-            ctx.activeRecurlyClientSubscription.id
-          )
-        ).to.be.true
-      })
-
-      it('should fail the invoice', async function (ctx) {
-        expect(
-          ctx.RecurlyClient.promises.failInvoice.calledWith(
-            ctx.pastDueInvoice.id
-          )
-        ).to.be.true
-      })
-
-      it('should call setSubscriptionWasReverted', async function (ctx) {
-        expect(
-          ctx.SubscriptionUpdater.promises.setSubscriptionWasReverted.calledWith(
-            ctx.subscriptionRestorePoint._id
-          )
-        ).to.be.true
-      })
-
-      it('should sync the subscription', async function (ctx) {
-        ctx.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal(
-          true
-        )
-        ctx.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal(
-          ctx.activeRecurlySubscription
-        )
-        ctx.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal(
-          ctx.user._id
-        )
-      })
-    })
-
-    describe('should throw an IndeterminateInvoiceError when', function () {
-      beforeEach(function (ctx) {
-        ctx.subscriptionRestorePoint = {
-          planCode: 'collaborator',
-          addOns: [
-            { addOnCode: 'addon-1', quantity: 1, unitAmountInCents: 500 },
-          ],
-          _id: 'restore-point-id',
-        }
-        ctx.RecurlyClient.promises.getSubscription.resolves(
-          ctx.activeRecurlyClientSubscription
-        )
-      })
-
-      it('finds a past due invoice older than 24 hours', async function (ctx) {
-        const oldInvoice = {
-          id: 'invoice-123',
-          dueAt: new Date(Date.now() - 25 * 60 * 60 * 1000), // 25 hours ago
-          collectionMethod: 'automatic',
-        }
-        ctx.RecurlyClient.promises.getPastDueInvoices.resolves([oldInvoice])
-
-        await expect(
-          ctx.SubscriptionHandler.promises.revertPlanChange(
-            ctx.activeRecurlyClientSubscription.id,
-            ctx.subscriptionRestorePoint
-          )
-        ).to.be.rejectedWith('cant determine invoice to fail for plan revert')
-      })
-
-      it('finds more than one past due invoice', async function (ctx) {
-        const invoices = [
-          {
-            id: 'invoice-123',
-            dueAt: new Date(),
-            collectionMethod: 'automatic',
-          },
-          {
-            id: 'invoice-456',
-            dueAt: new Date(),
-            collectionMethod: 'automatic',
-          },
-        ]
-        ctx.RecurlyClient.promises.getPastDueInvoices.resolves(invoices)
-
-        await expect(
-          ctx.SubscriptionHandler.promises.revertPlanChange(
-            ctx.activeRecurlyClientSubscription.id,
-            ctx.subscriptionRestorePoint
-          )
-        ).to.be.rejectedWith('cant determine invoice to fail for plan revert')
-      })
-
-      it('finds an invoice with a collectionMethod other than automatic', async function (ctx) {
-        const manualInvoice = {
-          id: 'invoice-123',
-          dueAt: new Date(),
-          collectionMethod: 'manual',
-        }
-        ctx.RecurlyClient.promises.getPastDueInvoices.resolves([manualInvoice])
-
-        await expect(
-          ctx.SubscriptionHandler.promises.revertPlanChange(
-            ctx.activeRecurlyClientSubscription.id,
-            ctx.subscriptionRestorePoint
-          )
-        ).to.be.rejectedWith('cant determine invoice to fail for plan revert')
-      })
-    })
-  })
 })

+ 0 - 93
services/web/test/unit/src/Subscription/SubscriptionUpdater.test.mjs

@@ -1038,97 +1038,4 @@ describe('SubscriptionUpdater', function () {
       ).to.equal(4)
     })
   })
-  describe('setRestorePoint', function () {
-    it('should set the restore point with the given plan code and add-ons', async function (ctx) {
-      const subscriptionId = new ObjectId()
-      const planCode = 'gold-plan'
-      const addOns = [
-        { addOnCode: 'addon-1', quantity: 2, unitAmountInCents: 500 },
-        { addOnCode: 'addon-2', quantity: 1, unitAmountInCents: 1000 },
-      ]
-      const consumed = false
-
-      await ctx.SubscriptionUpdater.promises.setRestorePoint(
-        subscriptionId,
-        planCode,
-        addOns,
-        consumed
-      )
-
-      sinon.assert.calledWith(
-        ctx.SubscriptionModel.updateOne,
-        { _id: subscriptionId },
-        {
-          $set: {
-            'lastSuccesfulSubscription.planCode': planCode,
-            'lastSuccesfulSubscription.addOns': addOns,
-          },
-        }
-      )
-    })
-
-    it('should increment revertedDueToFailedPayment if consumed is true', async function (ctx) {
-      const consumed = true
-      const subscriptionId = new ObjectId()
-
-      await ctx.SubscriptionUpdater.promises.setRestorePoint(
-        subscriptionId,
-        null,
-        null,
-        consumed
-      )
-
-      sinon.assert.calledWith(
-        ctx.SubscriptionModel.updateOne,
-        { _id: subscriptionId },
-        {
-          $set: {
-            'lastSuccesfulSubscription.planCode': null,
-            'lastSuccesfulSubscription.addOns': null,
-          },
-          $inc: { timesRevertedDueToFailedPayment: 1 },
-        }
-      )
-    })
-  })
-
-  describe('setSubscriptionWasReverted', function () {
-    it('should clear the restore point and mark the subscription as reverted', async function (ctx) {
-      const subscriptionId = new ObjectId().toString()
-
-      await ctx.SubscriptionUpdater.promises.setSubscriptionWasReverted(
-        subscriptionId
-      )
-
-      ctx.SubscriptionModel.updateOne.should.have.been.calledWith(
-        { _id: subscriptionId },
-        {
-          $set: {
-            'lastSuccesfulSubscription.planCode': null,
-            'lastSuccesfulSubscription.addOns': null,
-          },
-          $inc: { timesRevertedDueToFailedPayment: 1 },
-        }
-      )
-    })
-  })
-
-  describe('voidRestorePoint', function () {
-    it('should clear the restore point without marking the subscription as reverted', async function (ctx) {
-      const subscriptionId = new ObjectId().toString()
-
-      await ctx.SubscriptionUpdater.promises.voidRestorePoint(subscriptionId)
-
-      sinon.assert.calledWith(
-        ctx.SubscriptionModel.updateOne,
-        { _id: subscriptionId },
-        {
-          $set: {
-            'lastSuccesfulSubscription.planCode': null,
-            'lastSuccesfulSubscription.addOns': null,
-          },
-        }
-      )
-    })
-  })
 })