Răsfoiți Sursa

Merge pull request #21954 from overleaf/em-revert-ai-add-on-deploy

Revert faulty AI add-on deploy

GitOrigin-RevId: 923ec89f01d4951596f83fcf58b820c39db1e54a
Eric Mc Sween 1 an în urmă
părinte
comite
ab36c80a26

+ 0 - 27
package-lock.json

@@ -41611,7 +41611,6 @@
         "mocha": "^10.2.0",
         "mocha-each": "^2.0.1",
         "mock-fs": "^5.1.2",
-        "nock": "^13.5.6",
         "nvd3": "^1.8.6",
         "overleaf-editor-core": "*",
         "pdfjs-dist": "4.6.82",
@@ -43737,20 +43736,6 @@
         "@sinonjs/commons": "^1.7.0"
       }
     },
-    "services/web/node_modules/nock": {
-      "version": "13.5.6",
-      "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz",
-      "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==",
-      "dev": true,
-      "dependencies": {
-        "debug": "^4.1.0",
-        "json-stringify-safe": "^5.0.1",
-        "propagate": "^2.0.0"
-      },
-      "engines": {
-        "node": ">= 10.13"
-      }
-    },
     "services/web/node_modules/on-finished": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -51189,7 +51174,6 @@
         "mongoose": "8.5.3",
         "multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e",
         "nocache": "^2.1.0",
-        "nock": "^13.5.6",
         "node-fetch": "^2.7.0",
         "nodemailer": "^6.7.0",
         "nodemailer-ses-transport": "^1.5.1",
@@ -52760,17 +52744,6 @@
             }
           }
         },
-        "nock": {
-          "version": "13.5.6",
-          "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz",
-          "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==",
-          "dev": true,
-          "requires": {
-            "debug": "^4.1.0",
-            "json-stringify-safe": "^5.0.1",
-            "propagate": "^2.0.0"
-          }
-        },
         "on-finished": {
           "version": "2.4.1",
           "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",

+ 31 - 109
services/web/app/src/Features/Subscription/RecurlyClient.js

@@ -13,7 +13,6 @@ const {
   PaypalPaymentMethod,
   CreditCardPaymentMethod,
   RecurlyAddOn,
-  RecurlyPlan,
 } = require('./RecurlyEntities')
 
 /**
@@ -68,32 +67,6 @@ async function getSubscription(subscriptionId) {
   return subscriptionFromApi(subscription)
 }
 
-/**
- * Get the subscription for a given user
- *
- * @param {string} userId
- * @return {Promise<RecurlySubscription | null>}
- */
-async function getSubscriptionForUser(userId) {
-  const subscriptions = await client.listAccountSubscriptions(
-    `code-${userId}`,
-    { params: { state: 'active', limit: 2 } }
-  )
-  let result = null
-  for await (const subscription of subscriptions.each()) {
-    if (result != null) {
-      throw new OError('User has more than one Recurly subscription', {
-        userId,
-      })
-    }
-    result = subscription
-  }
-  if (result == null) {
-    return null
-  }
-  return subscriptionFromApi(result)
-}
-
 /**
  * Request a susbcription change from Recurly
  *
@@ -188,17 +161,6 @@ async function getAddOn(planCode, addOnCode) {
   return addOnFromApi(addOn)
 }
 
-/**
- * Get the configuration for a given plan
- *
- * @param {string} planCode
- * @return {Promise<RecurlyPlan>}
- */
-async function getPlan(planCode) {
-  const plan = await client.getPlan(`code-${planCode}`)
-  return planFromApi(plan)
-}
-
 function subscriptionIsCanceledOrExpired(subscription) {
   const state = subscription?.recurlyStatus?.state
   return state === 'canceled' || state === 'expired'
@@ -207,53 +169,41 @@ function subscriptionIsCanceledOrExpired(subscription) {
 /**
  * Build a RecurlySubscription from Recurly API data
  *
- * @param {recurly.Subscription} apiSubscription
+ * @param {recurly.Subscription} subscription
  * @return {RecurlySubscription}
  */
-function subscriptionFromApi(apiSubscription) {
+function subscriptionFromApi(subscription) {
   if (
-    apiSubscription.uuid == null ||
-    apiSubscription.plan == null ||
-    apiSubscription.plan.code == null ||
-    apiSubscription.plan.name == null ||
-    apiSubscription.account == null ||
-    apiSubscription.account.code == null ||
-    apiSubscription.unitAmount == null ||
-    apiSubscription.subtotal == null ||
-    apiSubscription.total == null ||
-    apiSubscription.currency == null ||
-    apiSubscription.currentPeriodStartedAt == null ||
-    apiSubscription.currentPeriodEndsAt == null
+    subscription.uuid == null ||
+    subscription.plan == null ||
+    subscription.plan.code == null ||
+    subscription.plan.name == null ||
+    subscription.account == null ||
+    subscription.account.code == null ||
+    subscription.unitAmount == null ||
+    subscription.subtotal == null ||
+    subscription.total == null ||
+    subscription.currency == null ||
+    subscription.currentPeriodStartedAt == null ||
+    subscription.currentPeriodEndsAt == null
   ) {
-    throw new OError('Invalid Recurly subscription', {
-      subscription: apiSubscription,
-    })
+    throw new OError('Invalid Recurly subscription', { subscription })
   }
-
-  const subscription = new RecurlySubscription({
-    id: apiSubscription.uuid,
-    userId: apiSubscription.account.code,
-    planCode: apiSubscription.plan.code,
-    planName: apiSubscription.plan.name,
-    planPrice: apiSubscription.unitAmount,
-    addOns: (apiSubscription.addOns ?? []).map(subscriptionAddOnFromApi),
-    subtotal: apiSubscription.subtotal,
-    taxRate: apiSubscription.taxInfo?.rate ?? 0,
-    taxAmount: apiSubscription.tax ?? 0,
-    total: apiSubscription.total,
-    currency: apiSubscription.currency,
-    periodStart: apiSubscription.currentPeriodStartedAt,
-    periodEnd: apiSubscription.currentPeriodEndsAt,
+  return new RecurlySubscription({
+    id: subscription.uuid,
+    userId: subscription.account.code,
+    planCode: subscription.plan.code,
+    planName: subscription.plan.name,
+    planPrice: subscription.unitAmount,
+    addOns: (subscription.addOns ?? []).map(subscriptionAddOnFromApi),
+    subtotal: subscription.subtotal,
+    taxRate: subscription.taxInfo?.rate ?? 0,
+    taxAmount: subscription.tax ?? 0,
+    total: subscription.total,
+    currency: subscription.currency,
+    periodStart: subscription.currentPeriodStartedAt,
+    periodEnd: subscription.currentPeriodEndsAt,
   })
-
-  if (apiSubscription.pendingChange != null) {
-    subscription.pendingChange = subscriptionChangeFromApi(
-      subscription,
-      apiSubscription.pendingChange
-    )
-  }
-
-  return subscription
 }
 
 /**
@@ -301,22 +251,14 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) {
   const nextAddOns = (subscriptionChange.addOns ?? []).map(
     subscriptionAddOnFromApi
   )
-
-  let immediateCharge =
-    subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0
-  for (const creditInvoice of subscriptionChange.invoiceCollection
-    ?.creditInvoices ?? []) {
-    // The credit invoice totals are already negative
-    immediateCharge += creditInvoice.total ?? 0
-  }
-
   return new RecurlySubscriptionChange({
     subscription,
     nextPlanCode: subscriptionChange.plan.code,
     nextPlanName: subscriptionChange.plan.name,
     nextPlanPrice: subscriptionChange.unitAmount,
     nextAddOns,
-    immediateCharge,
+    immediateCharge:
+      subscriptionChange.invoiceCollection?.chargeInvoice?.total ?? 0,
   })
 }
 
@@ -361,22 +303,6 @@ function addOnFromApi(addOn) {
   })
 }
 
-/**
- * Build a RecurlyPlan from Recurly API data
- *
- * @param {recurly.Plan} plan
- * @return {RecurlyPlan}
- */
-function planFromApi(plan) {
-  if (plan.code == null || plan.name == null) {
-    throw new OError('Invalid Recurly add-on', { plan })
-  }
-  return new RecurlyPlan({
-    code: plan.code,
-    name: plan.name,
-  })
-}
-
 /**
  * Build an API request from a RecurlySubscriptionChangeRequest
  *
@@ -413,7 +339,6 @@ module.exports = {
   getAccountForUserId: callbackify(getAccountForUserId),
   createAccountForUserId: callbackify(createAccountForUserId),
   getSubscription: callbackify(getSubscription),
-  getSubscriptionForUser: callbackify(getSubscriptionForUser),
   previewSubscriptionChange: callbackify(previewSubscriptionChange),
   applySubscriptionChangeRequest: callbackify(applySubscriptionChangeRequest),
   removeSubscriptionChange: callbackify(removeSubscriptionChange),
@@ -422,12 +347,10 @@ module.exports = {
   cancelSubscriptionByUuid: callbackify(cancelSubscriptionByUuid),
   getPaymentMethod: callbackify(getPaymentMethod),
   getAddOn: callbackify(getAddOn),
-  getPlan: callbackify(getPlan),
   subscriptionIsCanceledOrExpired,
 
   promises: {
     getSubscription,
-    getSubscriptionForUser,
     getAccountForUserId,
     createAccountForUserId,
     previewSubscriptionChange,
@@ -438,6 +361,5 @@ module.exports = {
     cancelSubscriptionByUuid,
     getPaymentMethod,
     getAddOn,
-    getPlan,
   },
 }

+ 4 - 82
services/web/app/src/Features/Subscription/RecurlyEntities.js

@@ -6,7 +6,6 @@ const PlansLocator = require('./PlansLocator')
 const SubscriptionHelper = require('./SubscriptionHelper')
 
 const AI_ADD_ON_CODE = 'assistant'
-const STANDALONE_AI_ADD_ON_CODES = ['assistant', 'assistant-annual']
 
 class RecurlySubscription {
   /**
@@ -24,7 +23,6 @@ class RecurlySubscription {
    * @param {number} props.total
    * @param {Date} props.periodStart
    * @param {Date} props.periodEnd
-   * @param {RecurlySubscriptionChange} [props.pendingChange]
    */
   constructor(props) {
     this.id = props.id
@@ -40,47 +38,12 @@ class RecurlySubscription {
     this.total = props.total
     this.periodStart = props.periodStart
     this.periodEnd = props.periodEnd
-    this.pendingChange = props.pendingChange ?? null
   }
 
-  /**
-   * Returns whether this subscription currently has the given add-on
-   *
-   * @param {string} code
-   * @return {boolean}
-   */
   hasAddOn(code) {
     return this.addOns.some(addOn => addOn.code === code)
   }
 
-  /**
-   * Returns whether this subscription is a standalone AI add-on subscription
-   *
-   * @return {boolean}
-   */
-  isStandaloneAiAddOn() {
-    return isStandaloneAiAddOnPlanCode(this.planCode)
-  }
-
-  /**
-   * Returns whether this subcription will have the given add-on next billing
-   * period.
-   *
-   * There are two cases: either the subscription already has the add-on and
-   * won't change next period, or the subscription will change next period and
-   * the change includes the add-on.
-   *
-   * @param {string} code
-   * @return {boolean}
-   */
-  hasAddOnNextPeriod(code) {
-    if (this.pendingChange != null) {
-      return this.pendingChange.nextAddOns.some(addOn => addOn.code === code)
-    } else {
-      return this.hasAddOn(code)
-    }
-  }
-
   /**
    * Change this subscription's plan
    *
@@ -97,31 +60,16 @@ class RecurlySubscription {
     if (newPlan == null) {
       throw new OError('Unable to find plan in settings', { planCode })
     }
-    const shouldChangeAtTermEnd = SubscriptionHelper.shouldPlanChangeAtTermEnd(
+    const changeAtTermEnd = SubscriptionHelper.shouldPlanChangeAtTermEnd(
       currentPlan,
       newPlan
     )
-
-    const changeRequest = new RecurlySubscriptionChangeRequest({
+    const timeframe = changeAtTermEnd ? 'term_end' : 'now'
+    return new RecurlySubscriptionChangeRequest({
       subscription: this,
-      timeframe: shouldChangeAtTermEnd ? 'term_end' : 'now',
+      timeframe,
       planCode,
     })
-
-    // Carry the AI add-on to the new plan if applicable
-    if (
-      this.isStandaloneAiAddOn() ||
-      (!shouldChangeAtTermEnd && this.hasAddOn(AI_ADD_ON_CODE)) ||
-      (shouldChangeAtTermEnd && this.hasAddOnNextPeriod(AI_ADD_ON_CODE))
-    ) {
-      const addOnUpdate = new RecurlySubscriptionAddOnUpdate({
-        code: AI_ADD_ON_CODE,
-        quantity: 1,
-      })
-      changeRequest.addOnUpdates = [addOnUpdate]
-    }
-
-    return changeRequest
   }
 
   /**
@@ -314,30 +262,6 @@ class RecurlyAddOn {
   }
 }
 
-/**
- * A plan configuration
- */
-class RecurlyPlan {
-  /**
-   * @param {object} props
-   * @param {string} props.code
-   * @param {string} props.name
-   */
-  constructor(props) {
-    this.code = props.code
-    this.name = props.name
-  }
-}
-
-/**
- * Returns whether the given plan code is a standalone AI plan
- *
- * @param {string} planCode
- */
-function isStandaloneAiAddOnPlanCode(planCode) {
-  return STANDALONE_AI_ADD_ON_CODES.includes(planCode)
-}
-
 module.exports = {
   AI_ADD_ON_CODE,
   RecurlySubscription,
@@ -348,6 +272,4 @@ module.exports = {
   PaypalPaymentMethod,
   CreditCardPaymentMethod,
   RecurlyAddOn,
-  RecurlyPlan,
-  isStandaloneAiAddOnPlanCode,
 }

+ 0 - 7
services/web/app/src/Features/Subscription/RecurlyWrapper.js

@@ -10,10 +10,6 @@ const Errors = require('../Errors/Errors')
 const SubscriptionErrors = require('./Errors')
 const { callbackify } = require('@overleaf/promise-utils')
 
-/**
- * @param accountId
- * @param newEmail
- */
 async function updateAccountEmailAddress(accountId, newEmail) {
   const data = {
     email: newEmail,
@@ -818,9 +814,6 @@ const promises = {
     }
   },
 
-  /**
-   * @param xml
-   */
   _parseXml(xml) {
     function convertDataTypes(data) {
       let key, value

+ 26 - 83
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -29,13 +29,9 @@ const HttpErrorHandler = require('../Errors/HttpErrorHandler')
 const { URLSearchParams } = require('url')
 const RecurlyClient = require('./RecurlyClient')
 const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
-const PlansLocator = require('./PlansLocator')
 
 /**
- * @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview'
  * @import { SubscriptionChangePreview } from '../../../../types/subscription/subscription-change-preview'
- * @import { RecurlySubscriptionChange } from './RecurlyEntities'
- * @import { PaymentMethod } from './types'
  */
 
 const groupPlanModalOptions = Settings.groupPlanModalOptions
@@ -520,17 +516,38 @@ async function previewAddonPurchase(req, res) {
   )
 
   /** @type {SubscriptionChangePreview} */
-  const changePreview = makeChangePreview(
-    {
+  const changePreview = {
+    change: {
       type: 'add-on-purchase',
       addOn: {
         code: addOn.code,
         name: addOn.name,
       },
     },
-    subscriptionChange,
-    paymentMethod
-  )
+    currency: subscription.currency,
+    immediateCharge: subscriptionChange.immediateCharge,
+    paymentMethod: paymentMethod.toString(),
+    nextInvoice: {
+      date: subscription.periodEnd.toISOString(),
+      plan: {
+        name: subscriptionChange.nextPlanName,
+        amount: subscriptionChange.nextPlanPrice,
+      },
+      addOns: subscriptionChange.nextAddOns.map(addOn => ({
+        code: addOn.code,
+        name: addOn.name,
+        quantity: addOn.quantity,
+        unitAmount: addOn.unitPrice,
+        amount: addOn.preTaxTotal,
+      })),
+      subtotal: subscriptionChange.subtotal,
+      tax: {
+        rate: subscription.taxRate,
+        amount: subscriptionChange.tax,
+      },
+      total: subscriptionChange.total,
+    },
+  }
 
   res.render('subscriptions/preview-change', { changePreview })
 }
@@ -602,31 +619,6 @@ async function removeAddon(req, res, next) {
   }
 }
 
-async function previewSubscription(req, res, next) {
-  const planCode = req.query.planCode
-  if (!planCode) {
-    return HttpErrorHandler.notFound(req, res, 'Missing plan code')
-  }
-  const plan = await RecurlyClient.promises.getPlan(planCode)
-  const userId = SessionManager.getLoggedInUserId(req.session)
-  const subscriptionChange =
-    await SubscriptionHandler.promises.previewSubscriptionChange(
-      userId,
-      planCode
-    )
-  const paymentMethod = await RecurlyClient.promises.getPaymentMethod(userId)
-  const changePreview = makeChangePreview(
-    {
-      type: 'premium-subscription',
-      plan: { code: plan.code, name: plan.name },
-    },
-    subscriptionChange,
-    paymentMethod
-  )
-
-  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)
@@ -895,54 +887,6 @@ async function getLatamCountryBannerDetails(req, res) {
   return latamCountryBannerDetails
 }
 
-/**
- * Build a subscription change preview for display purposes
- *
- * @param {SubscriptionChangeDescription} subscriptionChangeDescription A description of the change for the frontend
- * @param {RecurlySubscriptionChange} subscriptionChange The subscription change object coming from Recurly
- * @param {PaymentMethod} paymentMethod The payment method associated to the user
- * @return {SubscriptionChangePreview}
- */
-function makeChangePreview(
-  subscriptionChangeDescription,
-  subscriptionChange,
-  paymentMethod
-) {
-  const subscription = subscriptionChange.subscription
-  const nextPlan = PlansLocator.findLocalPlanInSettings(
-    subscriptionChange.nextPlanCode
-  )
-  return {
-    change: subscriptionChangeDescription,
-    currency: subscription.currency,
-    immediateCharge: subscriptionChange.immediateCharge,
-    paymentMethod: paymentMethod.toString(),
-    nextPlan: {
-      annual: nextPlan.annual ?? false,
-    },
-    nextInvoice: {
-      date: subscription.periodEnd.toISOString(),
-      plan: {
-        name: subscriptionChange.nextPlanName,
-        amount: subscriptionChange.nextPlanPrice,
-      },
-      addOns: subscriptionChange.nextAddOns.map(addOn => ({
-        code: addOn.code,
-        name: addOn.name,
-        quantity: addOn.quantity,
-        unitAmount: addOn.unitPrice,
-        amount: addOn.preTaxTotal,
-      })),
-      subtotal: subscriptionChange.subtotal,
-      tax: {
-        rate: subscription.taxRate,
-        amount: subscriptionChange.tax,
-      },
-      total: subscriptionChange.total,
-    },
-  }
-}
-
 module.exports = {
   plansPage: expressify(plansPage),
   plansPageLightDesign: expressify(plansPageLightDesign),
@@ -952,7 +896,6 @@ module.exports = {
   cancelSubscription,
   canceledSubscription: expressify(canceledSubscription),
   cancelV1Subscription,
-  previewSubscription: expressify(previewSubscription),
   updateSubscription,
   cancelPendingSubscriptionChange,
   updateAccountEmailAddress,

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

@@ -65,26 +65,6 @@ async function createSubscription(user, subscriptionDetails, recurlyTokenIds) {
   )
 }
 
-/**
- * Preview the effect of changing the subscription plan
- *
- * @param {string} userId
- * @param {string} planCode
- * @return {Promise<RecurlySubscriptionChange>}
- */
-async function previewSubscriptionChange(userId, planCode) {
-  const recurlyId = await getSubscriptionRecurlyId(userId)
-  if (recurlyId == null) {
-    throw new NoRecurlySubscriptionError('Subscription not found', { userId })
-  }
-
-  const subscription = await RecurlyClient.promises.getSubscription(recurlyId)
-  const changeRequest = subscription?.getRequestForPlanChange(planCode)
-  const change =
-    await RecurlyClient.promises.previewSubscriptionChange(changeRequest)
-  return change
-}
-
 /**
  * @param user
  * @param planCode
@@ -381,7 +361,6 @@ async function getSubscriptionRecurlyId(userId) {
 module.exports = {
   validateNoSubscriptionInRecurly: callbackify(validateNoSubscriptionInRecurly),
   createSubscription: callbackify(createSubscription),
-  previewSubscriptionChange: callbackify(previewSubscriptionChange),
   updateSubscription: callbackify(updateSubscription),
   cancelPendingSubscriptionChange: callbackify(cancelPendingSubscriptionChange),
   cancelSubscription: callbackify(cancelSubscription),
@@ -395,7 +374,6 @@ module.exports = {
   promises: {
     validateNoSubscriptionInRecurly,
     createSubscription,
-    previewSubscriptionChange,
     updateSubscription,
     cancelPendingSubscriptionChange,
     cancelSubscription,

+ 0 - 6
services/web/app/src/Features/Subscription/SubscriptionRouter.mjs

@@ -121,12 +121,6 @@ export default {
     )
 
     // user changes their account state
-    webRouter.get(
-      '/user/subscription/preview',
-      AuthenticationController.requireLogin(),
-      RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
-      SubscriptionController.previewSubscription
-    )
     webRouter.post(
       '/user/subscription/update',
       AuthenticationController.requireLogin(),

+ 0 - 2
services/web/frontend/extracted-translations.json

@@ -66,7 +66,6 @@
   "add_company_details": "",
   "add_email_address": "",
   "add_email_to_claim_features": "",
-  "add_error_assist_to_your_projects": "",
   "add_files": "",
   "add_more_collaborators": "",
   "add_more_editors": "",
@@ -1485,7 +1484,6 @@
   "submit_title": "",
   "subscribe": "",
   "subscribe_to_find_the_symbols_you_need_faster": "",
-  "subscribe_to_plan": "",
   "subscription": "",
   "subscription_admins_cannot_be_deleted": "",
   "subscription_canceled": "",

+ 10 - 2
services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx

@@ -7,7 +7,11 @@ import { ExpiredSubscription } from './states/expired'
 import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
 import PersonalSubscriptionRecurlySyncEmail from './personal-subscription-recurly-sync-email'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
-import { isStandaloneAiPlanCode, AI_ADD_ON_CODE } from '../../data/add-on-codes'
+import {
+  AI_STANDALONE_PLAN_CODE,
+  AI_STANDALONE_ANNUAL_PLAN_CODE,
+  AI_ADD_ON_CODE,
+} from '../../data/add-on-codes'
 
 function PastDueSubscriptionAlert({
   subscription,
@@ -46,7 +50,11 @@ function PersonalSubscriptionStates({
     addOn => addOn.addOnCode === AI_ADD_ON_CODE
   )
 
-  const onAiStandalonePlan = isStandaloneAiPlanCode(subscription.planCode)
+  const onAiStandalonePlan = [
+    AI_STANDALONE_PLAN_CODE,
+    AI_STANDALONE_ANNUAL_PLAN_CODE,
+  ].includes(subscription.planCode)
+
   const planHasAi = onAiStandalonePlan || hasAiAddon
 
   if (state === 'active' && planHasAi) {

+ 6 - 2
services/web/frontend/js/features/subscription/components/dashboard/states/active/active-ai-addon.tsx

@@ -15,7 +15,8 @@ import {
   ADD_ON_NAME,
   AI_ADD_ON_CODE,
   AI_STANDALONE_PLAN_NAME,
-  isStandaloneAiPlanCode,
+  AI_STANDALONE_PLAN_CODE,
+  AI_STANDALONE_ANNUAL_PLAN_CODE,
 } from '../../../../data/add-on-codes'
 import { CancelSubscriptionButton } from './cancel-subscription-button'
 
@@ -31,7 +32,10 @@ export function ActiveAiAddonSubscription({
     useSubscriptionDashboardContext()
   if (showCancellation) return <CancelSubscription />
 
-  const onStandalonePlan = isStandaloneAiPlanCode(subscription.planCode)
+  const onStandalonePlan = [
+    AI_STANDALONE_PLAN_CODE,
+    AI_STANDALONE_ANNUAL_PLAN_CODE,
+  ].includes(subscription.planCode)
 
   const handlePlanChange = () => setModalIdShown('change-plan')
 

+ 2 - 2
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal.tsx

@@ -14,8 +14,8 @@ import OLButton from '@/features/ui/components/ol/ol-button'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
 import {
   AI_ADD_ON_CODE,
+  AI_STANDALONE_PLAN_CODE,
   ADD_ON_NAME,
-  isStandaloneAiPlanCode,
 } from '../../../../../../data/add-on-codes'
 import {
   cancelSubscriptionUrl,
@@ -33,7 +33,7 @@ export function CancelAiAddOnModal() {
 
   if (!personalSubscription) return null
 
-  const onStandalone = isStandaloneAiPlanCode(personalSubscription.planCode)
+  const onStandalone = personalSubscription.planCode === AI_STANDALONE_PLAN_CODE
 
   const cancellationEndpoint = onStandalone
     ? cancelSubscriptionUrl

+ 3 - 11
services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx

@@ -31,17 +31,13 @@ function PreviewSubscriptionChange() {
       <Row>
         <Col md={8} mdOffset={2}>
           <div className="card p-5">
-            {preview.change.type === 'add-on-purchase' ? (
+            {preview.change.type === 'add-on-purchase' && (
               <h1>
                 {t('add_add_on_to_your_plan', {
                   addOnName: preview.change.addOn.name,
                 })}
               </h1>
-            ) : preview.change.type === 'premium-subscription' ? (
-              <h1>
-                {t('subscribe_to_plan', { planName: preview.change.plan.name })}
-              </h1>
-            ) : null}
+            )}
 
             {payNowTask.isError && (
               <Notification
@@ -115,11 +111,7 @@ function PreviewSubscriptionChange() {
               )}
 
               <Row className="mt-1">
-                <Col xs={9}>
-                  {preview.nextPlan.annual
-                    ? t('total_per_year')
-                    : t('total_per_month')}
-                </Col>
+                <Col xs={9}>{t('total_per_month')}</Col>
                 <Col xs={3} className="text-right">
                   {formatCurrencyLocalized(
                     preview.nextInvoice.total,

+ 1 - 5
services/web/frontend/js/features/subscription/data/add-on-codes.ts

@@ -3,8 +3,4 @@ export const AI_ADD_ON_CODE = 'assistant'
 // we dont want translations on plan or add-on names
 export const ADD_ON_NAME = "Error Assist"
 export const AI_STANDALONE_PLAN_NAME = "Overleaf Free"
-export const AI_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual'
-
-export function isStandaloneAiPlanCode(planCode: string) {
-  return planCode === AI_STANDALONE_PLAN_CODE || planCode === AI_STANDALONE_ANNUAL_PLAN_CODE
-}
+export const AI_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual'

+ 0 - 2
services/web/locales/en.json

@@ -78,7 +78,6 @@
   "add_email": "Add Email",
   "add_email_address": "Add email address",
   "add_email_to_claim_features": "Add an institutional email address to claim your features.",
-  "add_error_assist_to_your_projects": "Add Error Assist to your projects and get AI help to fix LaTeX errors faster.",
   "add_files": "Add Files",
   "add_more_collaborators": "Add more collaborators",
   "add_more_editors": "Add more editors",
@@ -2062,7 +2061,6 @@
   "submit_title": "Submit",
   "subscribe": "Subscribe",
   "subscribe_to_find_the_symbols_you_need_faster": "Subscribe to find the symbols you need faster",
-  "subscribe_to_plan": "Subscribe to __planName__",
   "subscription": "Subscription",
   "subscription_admin_panel": "admin panel",
   "subscription_admins_cannot_be_deleted": "You cannot delete your account while on a subscription. Please cancel your subscription and try again. If you keep seeing this message please contact us.",

+ 2 - 3
services/web/package.json

@@ -234,6 +234,7 @@
     "@types/chai": "^4.3.0",
     "@types/dateformat": "^5.0.2",
     "@types/diff": "^5.0.9",
+    "uuid": "^9.0.1",
     "@types/dompurify": "^3.0.5",
     "@types/events": "^3.0.0",
     "@types/express": "^4.17.13",
@@ -257,8 +258,8 @@
     "@uppy/react": "^3.2.1",
     "@uppy/utils": "^5.7.0",
     "@uppy/xhr-upload": "^3.6.0",
-    "5to6-codemod": "^1.8.0",
     "abort-controller": "^3.0.0",
+    "5to6-codemod": "^1.8.0",
     "acorn": "^7.1.1",
     "acorn-walk": "^7.1.1",
     "algoliasearch": "^3.35.1",
@@ -322,7 +323,6 @@
     "mocha": "^10.2.0",
     "mocha-each": "^2.0.1",
     "mock-fs": "^5.1.2",
-    "nock": "^13.5.6",
     "nvd3": "^1.8.6",
     "overleaf-editor-core": "*",
     "pdfjs-dist": "4.6.82",
@@ -363,7 +363,6 @@
     "to-string-loader": "^1.2.0",
     "tty-browserify": "^0.0.1",
     "typescript": "^5.0.4",
-    "uuid": "^9.0.1",
     "w3c-keyname": "^2.2.8",
     "webpack": "^5.93.0",
     "webpack-assets-manifest": "^5.2.1",

+ 2 - 3
services/web/test/unit/src/Subscription/RecurlyClientTests.js

@@ -3,7 +3,6 @@ const { expect } = require('chai')
 const recurly = require('recurly')
 const SandboxedModule = require('sandboxed-module')
 const {
-  RecurlySubscription,
   RecurlySubscriptionChangeRequest,
   RecurlySubscriptionAddOnUpdate,
 } = require('../../../../app/src/Features/Subscription/RecurlyEntities')
@@ -36,7 +35,7 @@ describe('RecurlyClient', function () {
       preTaxTotal: 2,
     }
 
-    this.subscription = new RecurlySubscription({
+    this.subscription = {
       id: 'subscription-id',
       userId: 'user-id',
       currency: 'EUR',
@@ -50,7 +49,7 @@ describe('RecurlyClient', function () {
       total: 16.5,
       periodStart: new Date(),
       periodEnd: new Date(),
-    })
+    }
 
     this.recurlySubscription = {
       uuid: this.subscription.id,

+ 0 - 64
services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js

@@ -4,11 +4,9 @@ const SandboxedModule = require('sandboxed-module')
 const { expect } = require('chai')
 const Errors = require('../../../../app/src/Features/Subscription/Errors')
 const {
-  AI_ADD_ON_CODE,
   RecurlySubscriptionChangeRequest,
   RecurlySubscriptionChange,
   RecurlySubscription,
-  RecurlySubscriptionAddOnUpdate,
 } = require('../../../../app/src/Features/Subscription/RecurlyEntities')
 
 const MODULE_PATH = '../../../../app/src/Features/Subscription/RecurlyEntities'
@@ -18,7 +16,6 @@ describe('RecurlyEntities', function () {
     beforeEach(function () {
       this.Settings = {
         plans: [
-          { planCode: 'assistant-annual', price_in_cents: 5900 },
           { planCode: 'cheap-plan', price_in_cents: 500 },
           { planCode: 'regular-plan', price_in_cents: 1000 },
           { planCode: 'premium-plan', price_in_cents: 2000 },
@@ -95,67 +92,6 @@ describe('RecurlyEntities', function () {
             })
           )
         })
-
-        it('preserves the AI add-on on upgrades', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
-          this.addOn.code = AI_ADD_ON_CODE
-          const changeRequest =
-            this.subscription.getRequestForPlanChange('premium-plan')
-          expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
-              subscription: this.subscription,
-              timeframe: 'now',
-              planCode: 'premium-plan',
-              addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
-                  code: AI_ADD_ON_CODE,
-                  quantity: 1,
-                }),
-              ],
-            })
-          )
-        })
-
-        it('preserves the AI add-on on downgrades', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
-          this.addOn.code = AI_ADD_ON_CODE
-          const changeRequest =
-            this.subscription.getRequestForPlanChange('cheap-plan')
-          expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
-              subscription: this.subscription,
-              timeframe: 'term_end',
-              planCode: 'cheap-plan',
-              addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
-                  code: AI_ADD_ON_CODE,
-                  quantity: 1,
-                }),
-              ],
-            })
-          )
-        })
-
-        it('preserves the AI add-on on upgrades from the standalone AI plan', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
-          this.subscription.planCode = 'assistant-annual'
-          this.subscription.addOns = []
-          const changeRequest =
-            this.subscription.getRequestForPlanChange('cheap-plan')
-          expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
-              subscription: this.subscription,
-              timeframe: 'term_end',
-              planCode: 'cheap-plan',
-              addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
-                  code: AI_ADD_ON_CODE,
-                  quantity: 1,
-                }),
-              ],
-            })
-          )
-        })
       })
 
       describe('getRequestForAddOnPurchase()', function () {

+ 2 - 13
services/web/types/subscription/subscription-change-preview.ts

@@ -1,11 +1,8 @@
 export type SubscriptionChangePreview = {
-  change: SubscriptionChangeDescription
+  change: SubscriptionChange
   currency: string
   paymentMethod: string
   immediateCharge: number
-  nextPlan: {
-    annual: boolean
-  }
   nextInvoice: {
     date: string
     plan: {
@@ -30,7 +27,7 @@ type AddOn = {
   amount: number
 }
 
-export type SubscriptionChangeDescription = AddOnPurchase | PremiumSubscription
+type SubscriptionChange = AddOnPurchase
 
 type AddOnPurchase = {
   type: 'add-on-purchase'
@@ -39,11 +36,3 @@ type AddOnPurchase = {
     name: string
   }
 }
-
-type PremiumSubscription = {
-  type: 'premium-subscription'
-  plan: {
-    code: string
-    name: string
-  }
-}