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

Merge pull request #24680 from overleaf/kh-rename-recurly-namespace

[web] rename recurly namespace

GitOrigin-RevId: b7cfd26923d47bd7f3de4140be24d2d1ef20f6c8
Kristina 1 год назад
Родитель
Сommit
9f22564ca3
46 измененных файлов с 514 добавлено и 496 удалено
  1. 1 1
      services/web/app/src/Features/Project/ProjectController.js
  2. 1 1
      services/web/app/src/Features/Subscription/FeaturesUpdater.js
  3. 51 44
      services/web/app/src/Features/Subscription/PaymentProviderEntities.js
  4. 4 4
      services/web/app/src/Features/Subscription/PaymentService.js
  5. 45 45
      services/web/app/src/Features/Subscription/RecurlyClient.js
  6. 1 1
      services/web/app/src/Features/Subscription/RecurlyEventHandler.js
  7. 7 5
      services/web/app/src/Features/Subscription/SubscriptionController.js
  8. 1 1
      services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js
  9. 4 4
      services/web/app/src/Features/Subscription/SubscriptionHandler.js
  10. 1 1
      services/web/app/src/Features/Subscription/SubscriptionLocator.js
  11. 41 50
      services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js
  12. 4 1
      services/web/app/src/Features/Subscription/types.ts
  13. 2 2
      services/web/app/views/subscriptions/dashboard-react.pug
  14. 5 5
      services/web/frontend/js/features/subscription/components/dashboard/pause-modal.tsx
  15. 2 2
      services/web/frontend/js/features/subscription/components/dashboard/personal-subscription-recurly-sync-email.tsx
  16. 8 8
      services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx
  17. 26 26
      services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx
  18. 16 16
      services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx
  19. 5 5
      services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx
  20. 4 4
      services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx
  21. 9 9
      services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx
  22. 5 5
      services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx
  23. 2 2
      services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx
  24. 3 3
      services/web/frontend/js/features/subscription/components/dashboard/states/active/confirm-unpause-modal.tsx
  25. 3 3
      services/web/frontend/js/features/subscription/components/dashboard/states/active/flash-message.tsx
  26. 4 4
      services/web/frontend/js/features/subscription/components/dashboard/states/active/paused.tsx
  27. 9 9
      services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx
  28. 7 7
      services/web/frontend/js/features/subscription/components/dashboard/states/active/subscription-remainder.tsx
  29. 4 4
      services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx
  30. 3 3
      services/web/frontend/js/features/subscription/components/dashboard/states/expired.tsx
  31. 3 3
      services/web/frontend/js/features/subscription/components/shared/price-exceptions.tsx
  32. 6 6
      services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx
  33. 12 12
      services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx
  34. 4 4
      services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx
  35. 18 18
      services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
  36. 21 21
      services/web/test/frontend/features/subscription/fixtures/subscriptions.ts
  37. 2 2
      services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx
  38. 1 1
      services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js
  39. 64 56
      services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js
  40. 7 7
      services/web/test/unit/src/Subscription/PaymentServiceTests.js
  41. 16 16
      services/web/test/unit/src/Subscription/RecurlyClientTests.js
  42. 17 16
      services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js
  43. 6 6
      services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js
  44. 41 40
      services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js
  45. 14 9
      services/web/types/subscription/dashboard/subscription.ts
  46. 4 4
      services/web/types/subscription/plan.ts

+ 1 - 1
services/web/app/src/Features/Project/ProjectController.js

@@ -49,7 +49,7 @@ const Modules = require('../../infrastructure/Modules')
 const UserGetter = require('../User/UserGetter')
 const {
   isStandaloneAiAddOnPlanCode,
-} = require('../Subscription/RecurlyEntities')
+} = require('../Subscription/PaymentProviderEntities')
 const SubscriptionController = require('../Subscription/SubscriptionController.js')
 const { formatCurrency } = require('../../util/currency')
 

+ 1 - 1
services/web/app/src/Features/Subscription/FeaturesUpdater.js

@@ -14,7 +14,7 @@ const UserGetter = require('../User/UserGetter')
 const AnalyticsManager = require('../Analytics/AnalyticsManager')
 const Queues = require('../../infrastructure/Queues')
 const Modules = require('../../infrastructure/Modules')
-const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
+const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
 
 /**
  * Enqueue a job for refreshing features for the given user

+ 51 - 44
services/web/app/src/Features/Subscription/RecurlyEntities.js → services/web/app/src/Features/Subscription/PaymentProviderEntities.js

@@ -9,7 +9,7 @@ const AI_ADD_ON_CODE = 'assistant'
 const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license'
 const STANDALONE_AI_ADD_ON_CODES = ['assistant', 'assistant-annual']
 
-class RecurlySubscription {
+class PaymentProviderSubscription {
   /**
    * @param {object} props
    * @param {string} props.id
@@ -17,7 +17,7 @@ class RecurlySubscription {
    * @param {string} props.planCode
    * @param {string} props.planName
    * @param {number} props.planPrice
-   * @param {RecurlySubscriptionAddOn[]} [props.addOns]
+   * @param {PaymentProviderSubscriptionAddOn[]} [props.addOns]
    * @param {number} props.subtotal
    * @param {number} [props.taxRate]
    * @param {number} [props.taxAmount]
@@ -26,7 +26,7 @@ class RecurlySubscription {
    * @param {Date} props.periodStart
    * @param {Date} props.periodEnd
    * @param {string} props.collectionMethod
-   * @param {RecurlySubscriptionChange} [props.pendingChange]
+   * @param {PaymentProviderSubscriptionChange} [props.pendingChange]
    * @param {string} [props.service]
    * @param {string} [props.state]
    * @param {Date|null} [props.trialPeriodEnd]
@@ -97,7 +97,7 @@ class RecurlySubscription {
   /**
    * Change this subscription's plan
    *
-   * @return {RecurlySubscriptionChangeRequest}
+   * @return {PaymentProviderSubscriptionChangeRequest}
    */
   getRequestForPlanChange(planCode) {
     const currentPlan = PlansLocator.findLocalPlanInSettings(this.planCode)
@@ -115,7 +115,7 @@ class RecurlySubscription {
       newPlan
     )
 
-    const changeRequest = new RecurlySubscriptionChangeRequest({
+    const changeRequest = new PaymentProviderSubscriptionChangeRequest({
       subscription: this,
       timeframe: shouldChangeAtTermEnd ? 'term_end' : 'now',
       planCode,
@@ -127,7 +127,7 @@ class RecurlySubscription {
       (!shouldChangeAtTermEnd && this.hasAddOn(AI_ADD_ON_CODE)) ||
       (shouldChangeAtTermEnd && this.hasAddOnNextPeriod(AI_ADD_ON_CODE))
     ) {
-      const addOnUpdate = new RecurlySubscriptionAddOnUpdate({
+      const addOnUpdate = new PaymentProviderSubscriptionAddOnUpdate({
         code: AI_ADD_ON_CODE,
         quantity: 1,
       })
@@ -143,7 +143,7 @@ class RecurlySubscription {
    * @param {string} code
    * @param {number} [quantity]
    * @param {number} [unitPrice]
-   * @return {RecurlySubscriptionChangeRequest} - the change request to send to
+   * @return {PaymentProviderSubscriptionChangeRequest} - the change request to send to
    * Recurly
    *
    * @throws {DuplicateAddOnError} if the add-on is already present on the subscription
@@ -158,9 +158,9 @@ class RecurlySubscription {
 
     const addOnUpdates = this.addOns.map(addOn => addOn.toAddOnUpdate())
     addOnUpdates.push(
-      new RecurlySubscriptionAddOnUpdate({ code, quantity, unitPrice })
+      new PaymentProviderSubscriptionAddOnUpdate({ code, quantity, unitPrice })
     )
-    return new RecurlySubscriptionChangeRequest({
+    return new PaymentProviderSubscriptionChangeRequest({
       subscription: this,
       timeframe: 'now',
       addOnUpdates,
@@ -172,7 +172,7 @@ class RecurlySubscription {
    *
    * @param {string} code
    * @param {number} quantity
-   * @return {RecurlySubscriptionChangeRequest} - the change request to send to
+   * @return {PaymentProviderSubscriptionChangeRequest} - the change request to send to
    * Recurly
    *
    * @throws {AddOnNotPresentError} if the subscription doesn't have the add-on
@@ -198,7 +198,7 @@ class RecurlySubscription {
       return update
     })
 
-    return new RecurlySubscriptionChangeRequest({
+    return new PaymentProviderSubscriptionChangeRequest({
       subscription: this,
       timeframe: 'now',
       addOnUpdates,
@@ -209,7 +209,7 @@ class RecurlySubscription {
    * Remove an add-on from this subscription
    *
    * @param {string} code
-   * @return {RecurlySubscriptionChangeRequest}
+   * @return {PaymentProviderSubscriptionChangeRequest}
    *
    * @throws {AddOnNotPresentError} if the subscription doesn't have the add-on
    */
@@ -226,7 +226,7 @@ class RecurlySubscription {
     const addOnUpdates = this.addOns
       .filter(addOn => addOn.code !== code)
       .map(addOn => addOn.toAddOnUpdate())
-    return new RecurlySubscriptionChangeRequest({
+    return new PaymentProviderSubscriptionChangeRequest({
       subscription: this,
       timeframe: 'term_end',
       addOnUpdates,
@@ -237,19 +237,19 @@ class RecurlySubscription {
    * Upgrade group plan with the plan code provided
    *
    * @param {string} newPlanCode
-   * @return {RecurlySubscriptionChangeRequest}
+   * @return {PaymentProviderSubscriptionChangeRequest}
    */
   getRequestForGroupPlanUpgrade(newPlanCode) {
     // Ensure all the existing add-ons are added to the new plan
     const addOns = this.addOns.map(
       addOn =>
-        new RecurlySubscriptionAddOnUpdate({
+        new PaymentProviderSubscriptionAddOnUpdate({
           code: addOn.code,
           quantity: addOn.quantity,
         })
     )
 
-    return new RecurlySubscriptionChangeRequest({
+    return new PaymentProviderSubscriptionChangeRequest({
       subscription: this,
       timeframe: 'now',
       addOnUpdates: addOns,
@@ -270,7 +270,7 @@ class RecurlySubscription {
 /**
  * An add-on attached to a subscription
  */
-class RecurlySubscriptionAddOn {
+class PaymentProviderSubscriptionAddOn {
   /**
    * @param {object} props
    * @param {string} props.code
@@ -290,7 +290,7 @@ class RecurlySubscriptionAddOn {
    * Return an add-on update that doesn't modify the add-on
    */
   toAddOnUpdate() {
-    return new RecurlySubscriptionAddOnUpdate({
+    return new PaymentProviderSubscriptionAddOnUpdate({
       code: this.code,
       quantity: this.quantity,
       unitPrice: this.unitPrice,
@@ -298,17 +298,19 @@ class RecurlySubscriptionAddOn {
   }
 }
 
-class RecurlySubscriptionChangeRequest {
+class PaymentProviderSubscriptionChangeRequest {
   /**
    * @param {object} props
-   * @param {RecurlySubscription} props.subscription
+   * @param {PaymentProviderSubscription} props.subscription
    * @param {"now" | "term_end"} props.timeframe
    * @param {string} [props.planCode]
-   * @param {RecurlySubscriptionAddOnUpdate[]} [props.addOnUpdates]
+   * @param {PaymentProviderSubscriptionAddOnUpdate[]} [props.addOnUpdates]
    */
   constructor(props) {
     if (props.planCode == null && props.addOnUpdates == null) {
-      throw new OError('Invalid RecurlySubscriptionChangeRequest', { props })
+      throw new OError('Invalid PaymentProviderSubscriptionChangeRequest', {
+        props,
+      })
     }
     this.subscription = props.subscription
     this.timeframe = props.timeframe
@@ -317,7 +319,7 @@ class RecurlySubscriptionChangeRequest {
   }
 }
 
-class RecurlySubscriptionAddOnUpdate {
+class PaymentProviderSubscriptionAddOnUpdate {
   /**
    * @param {object} props
    * @param {string} props.code
@@ -331,15 +333,15 @@ class RecurlySubscriptionAddOnUpdate {
   }
 }
 
-class RecurlySubscriptionChange {
+class PaymentProviderSubscriptionChange {
   /**
    * @param {object} props
-   * @param {RecurlySubscription} props.subscription
+   * @param {PaymentProviderSubscription} props.subscription
    * @param {string} props.nextPlanCode
    * @param {string} props.nextPlanName
    * @param {number} props.nextPlanPrice
-   * @param {RecurlySubscriptionAddOn[]} props.nextAddOns
-   * @param {RecurlyImmediateCharge} [props.immediateCharge]
+   * @param {PaymentProviderSubscriptionAddOn[]} props.nextAddOns
+   * @param {PaymentProviderImmediateCharge} [props.immediateCharge]
    */
   constructor(props) {
     this.subscription = props.subscription
@@ -349,7 +351,12 @@ class RecurlySubscriptionChange {
     this.nextAddOns = props.nextAddOns
     this.immediateCharge =
       props.immediateCharge ??
-      new RecurlyImmediateCharge({ subtotal: 0, tax: 0, total: 0, discount: 0 })
+      new PaymentProviderImmediateCharge({
+        subtotal: 0,
+        tax: 0,
+        total: 0,
+        discount: 0,
+      })
 
     this.subtotal = this.nextPlanPrice
     for (const addOn of this.nextAddOns) {
@@ -388,7 +395,7 @@ class CreditCardPaymentMethod {
   }
 }
 
-class RecurlyImmediateCharge {
+class PaymentProviderImmediateCharge {
   /**
    * @param {object} props
    * @param {number} props.subtotal
@@ -407,7 +414,7 @@ class RecurlyImmediateCharge {
 /**
  * An add-on configuration, independent of any subscription
  */
-class RecurlyAddOn {
+class PaymentProviderAddOn {
   /**
    * @param {object} props
    * @param {string} props.code
@@ -422,7 +429,7 @@ class RecurlyAddOn {
 /**
  * A plan configuration
  */
-class RecurlyPlan {
+class PaymentProviderPlan {
   /**
    * @param {object} props
    * @param {string} props.code
@@ -437,7 +444,7 @@ class RecurlyPlan {
 /**
  * A coupon in the payment provider
  */
-class RecurlyCoupon {
+class PaymentProviderCoupon {
   /**
    * @param {object} props
    * @param {string} props.code
@@ -454,7 +461,7 @@ class RecurlyCoupon {
 /**
  * An account in the payment provider
  */
-class RecurlyAccount {
+class PaymentProviderAccount {
   /**
    * @param {object} props
    * @param {string} props.code
@@ -480,7 +487,7 @@ function isStandaloneAiAddOnPlanCode(planCode) {
 /**
  * Returns whether subscription change will have have the ai bundle once the change is processed
  *
- * @param {RecurlySubscriptionChange} subscriptionChange The subscription change object coming from Recurly
+ * @param {PaymentProviderSubscriptionChange} subscriptionChange The subscription change object coming from payment provider
  *
  * @return {boolean}
  */
@@ -497,18 +504,18 @@ module.exports = {
   AI_ADD_ON_CODE,
   MEMBERS_LIMIT_ADD_ON_CODE,
   STANDALONE_AI_ADD_ON_CODES,
-  RecurlySubscription,
-  RecurlySubscriptionAddOn,
-  RecurlySubscriptionChange,
-  RecurlySubscriptionChangeRequest,
-  RecurlySubscriptionAddOnUpdate,
+  PaymentProviderSubscription,
+  PaymentProviderSubscriptionAddOn,
+  PaymentProviderSubscriptionChange,
+  PaymentProviderSubscriptionChangeRequest,
+  PaymentProviderSubscriptionAddOnUpdate,
   PaypalPaymentMethod,
   CreditCardPaymentMethod,
-  RecurlyAddOn,
-  RecurlyPlan,
-  RecurlyCoupon,
-  RecurlyAccount,
+  PaymentProviderAddOn,
+  PaymentProviderPlan,
+  PaymentProviderCoupon,
+  PaymentProviderAccount,
   isStandaloneAiAddOnPlanCode,
   subscriptionChangeIsAiAssistUpgrade,
-  RecurlyImmediateCharge,
+  PaymentProviderImmediateCharge,
 }

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

@@ -5,7 +5,7 @@ const logger = require('@overleaf/logger')
 const { callbackify } = require('util')
 
 /**
- * @import { RecurlySubscription, RecurlyAccount, RecurlyCoupon } from "./RecurlyEntities"
+ * @import { PaymentProviderSubscription, PaymentProviderAccount, PaymentProviderCoupon } from "./PaymentProviderEntities.js"
  * @import { ObjectId } from 'mongodb'
  */
 
@@ -19,9 +19,9 @@ const { callbackify } = require('util')
 
 /**
  * @typedef {object} PaymentRecord
- * @property {RecurlySubscription} subscription
- * @property {RecurlyAccount | null} account
- * @property {RecurlyCoupon[]} coupons
+ * @property {PaymentProviderSubscription} subscription
+ * @property {PaymentProviderAccount | null} account
+ * @property {PaymentProviderCoupon[]} coupons
  */
 
 /**

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

@@ -7,24 +7,24 @@ const OError = require('@overleaf/o-error')
 const { callbackify } = require('util')
 const UserGetter = require('../User/UserGetter')
 const {
-  RecurlySubscription,
-  RecurlySubscriptionAddOn,
-  RecurlySubscriptionChange,
+  PaymentProviderSubscription,
+  PaymentProviderSubscriptionAddOn,
+  PaymentProviderSubscriptionChange,
   PaypalPaymentMethod,
   CreditCardPaymentMethod,
-  RecurlyAddOn,
-  RecurlyPlan,
-  RecurlyCoupon,
-  RecurlyAccount,
-  RecurlyImmediateCharge,
-} = require('./RecurlyEntities')
+  PaymentProviderAddOn,
+  PaymentProviderPlan,
+  PaymentProviderCoupon,
+  PaymentProviderAccount,
+  PaymentProviderImmediateCharge,
+} = require('./PaymentProviderEntities')
 const {
   MissingBillingInfoError,
   SubtotalLimitExceededError,
 } = require('./Errors')
 
 /**
- * @import { RecurlySubscriptionChangeRequest } from './RecurlyEntities'
+ * @import { PaymentProviderSubscriptionChangeRequest } from './PaymentProviderEntities'
  * @import { PaymentMethod } from './types'
  */
 
@@ -37,7 +37,7 @@ const client = new recurly.Client(recurlyApiKey)
  * Get account for a given user
  *
  * @param {string} userId
- * @return {Promise<RecurlyAccount | null>}
+ * @return {Promise<PaymentProviderAccount | null>}
  */
 async function getAccountForUserId(userId) {
   try {
@@ -76,7 +76,7 @@ async function createAccountForUserId(userId) {
  * Get active coupons for a given user
  *
  * @param {string} userId
- * @return {Promise<RecurlyCoupon[]>}
+ * @return {Promise<PaymentProviderCoupon[]>}
  */
 async function getActiveCouponsForUserId(userId) {
   try {
@@ -104,7 +104,7 @@ async function getActiveCouponsForUserId(userId) {
  * Get a subscription from Recurly
  *
  * @param {string} subscriptionId
- * @return {Promise<RecurlySubscription>}
+ * @return {Promise<PaymentProviderSubscription>}
  */
 async function getSubscription(subscriptionId) {
   const subscription = await client.getSubscription(`uuid-${subscriptionId}`)
@@ -118,7 +118,7 @@ async function getSubscription(subscriptionId) {
  * error if the user has more than one subscription.
  *
  * @param {string} userId
- * @return {Promise<RecurlySubscription | null>}
+ * @return {Promise<PaymentProviderSubscription | null>}
  */
 async function getSubscriptionForUser(userId) {
   try {
@@ -153,7 +153,7 @@ async function getSubscriptionForUser(userId) {
 /**
  * Request a susbcription change from Recurly
  *
- * @param {RecurlySubscriptionChangeRequest} changeRequest
+ * @param {PaymentProviderSubscriptionChangeRequest} changeRequest
  */
 async function applySubscriptionChangeRequest(changeRequest) {
   const body = subscriptionChangeRequestToApi(changeRequest)
@@ -193,8 +193,8 @@ async function applySubscriptionChangeRequest(changeRequest) {
 /**
  * Preview a subscription change
  *
- * @param {RecurlySubscriptionChangeRequest} changeRequest
- * @return {Promise<RecurlySubscriptionChange>}
+ * @param {PaymentProviderSubscriptionChangeRequest} changeRequest
+ * @return {Promise<PaymentProviderSubscriptionChange>}
  */
 async function previewSubscriptionChange(changeRequest) {
   const body = subscriptionChangeRequestToApi(changeRequest)
@@ -303,7 +303,7 @@ async function getPaymentMethod(userId) {
  *
  * @param {string} planCode
  * @param {string} addOnCode
- * @return {Promise<RecurlyAddOn>}
+ * @return {Promise<PaymentProviderAddOn>}
  */
 async function getAddOn(planCode, addOnCode) {
   const addOn = await client.getPlanAddOn(
@@ -317,7 +317,7 @@ async function getAddOn(planCode, addOnCode) {
  * Get the configuration for a given plan
  *
  * @param {string} planCode
- * @return {Promise<RecurlyPlan>}
+ * @return {Promise<PaymentProviderPlan>}
  */
 async function getPlan(planCode) {
   const plan = await client.getPlan(`code-${planCode}`)
@@ -330,10 +330,10 @@ function subscriptionIsCanceledOrExpired(subscription) {
 }
 
 /**
- * Build a RecurlyAccount from Recurly API data
+ * Build a PaymentProviderAccount from Recurly API data
  *
  * @param {recurly.Account} apiAccount
- * @return {RecurlyAccount}
+ * @return {PaymentProviderAccount}
  */
 function accountFromApi(apiAccount) {
   if (apiAccount.code == null || apiAccount.email == null) {
@@ -341,7 +341,7 @@ function accountFromApi(apiAccount) {
       account: apiAccount,
     })
   }
-  return new RecurlyAccount({
+  return new PaymentProviderAccount({
     code: apiAccount.code,
     email: apiAccount.email,
     hasPastDueInvoice: apiAccount.hasPastDueInvoice ?? false,
@@ -349,10 +349,10 @@ function accountFromApi(apiAccount) {
 }
 
 /**
- * Build a RecurlyCoupon from Recurly API data
+ * Build a PaymentProviderCoupon from Recurly API data
  *
  * @param {recurly.CouponRedemption} apiRedemption
- * @return {RecurlyCoupon}
+ * @return {PaymentProviderCoupon}
  */
 function couponFromApi(apiRedemption) {
   if (apiRedemption.coupon == null || apiRedemption.coupon.code == null) {
@@ -360,7 +360,7 @@ function couponFromApi(apiRedemption) {
       coupon: apiRedemption,
     })
   }
-  return new RecurlyCoupon({
+  return new PaymentProviderCoupon({
     code: apiRedemption.coupon.code,
     name: apiRedemption.coupon.name ?? '',
     description: apiRedemption.coupon.hostedPageDescription ?? '',
@@ -368,10 +368,10 @@ function couponFromApi(apiRedemption) {
 }
 
 /**
- * Build a RecurlySubscription from Recurly API data
+ * Build a PaymentProviderSubscription from Recurly API data
  *
  * @param {recurly.Subscription} apiSubscription
- * @return {RecurlySubscription}
+ * @return {PaymentProviderSubscription}
  */
 function subscriptionFromApi(apiSubscription) {
   if (
@@ -394,7 +394,7 @@ function subscriptionFromApi(apiSubscription) {
     })
   }
 
-  const subscription = new RecurlySubscription({
+  const subscription = new PaymentProviderSubscription({
     id: apiSubscription.uuid,
     userId: apiSubscription.account.code,
     planCode: apiSubscription.plan.code,
@@ -427,10 +427,10 @@ function subscriptionFromApi(apiSubscription) {
 }
 
 /**
- * Build a RecurlySubscriptionAddOn from Recurly API data
+ * Build a PaymentProviderSubscriptionAddOn from Recurly API data
  *
  * @param {recurly.SubscriptionAddOn} addOn
- * @return {RecurlySubscriptionAddOn}
+ * @return {PaymentProviderSubscriptionAddOn}
  */
 function subscriptionAddOnFromApi(addOn) {
   if (
@@ -442,7 +442,7 @@ function subscriptionAddOnFromApi(addOn) {
     throw new OError('Invalid Recurly add-on', { addOn })
   }
 
-  return new RecurlySubscriptionAddOn({
+  return new PaymentProviderSubscriptionAddOn({
     code: addOn.addOn.code,
     name: addOn.addOn.name,
     quantity: addOn.quantity ?? 1,
@@ -451,11 +451,11 @@ function subscriptionAddOnFromApi(addOn) {
 }
 
 /**
- * Build a RecurlySubscriptionChange from Recurly API data
+ * Build a PaymentProviderSubscriptionChange from Recurly API data
  *
- * @param {RecurlySubscription} subscription - the current subscription
+ * @param {PaymentProviderSubscription} subscription - the current subscription
  * @param {recurly.SubscriptionChange} subscriptionChange - the subscription change returned from the API
- * @return {RecurlySubscriptionChange}
+ * @return {PaymentProviderSubscriptionChange}
  */
 function subscriptionChangeFromApi(subscription, subscriptionChange) {
   if (
@@ -472,7 +472,7 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) {
     subscriptionAddOnFromApi
   )
 
-  return new RecurlySubscriptionChange({
+  return new PaymentProviderSubscriptionChange({
     subscription,
     nextPlanCode: subscriptionChange.plan.code,
     nextPlanName: subscriptionChange.plan.name,
@@ -486,7 +486,7 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) {
  * Compute immediate charge based on invoice collection
  *
  * @param {recurly.SubscriptionChange} subscriptionChange - the subscription change returned from the API
- * @return {RecurlyImmediateCharge}
+ * @return {PaymentProviderImmediateCharge}
  */
 function computeImmediateCharge(subscriptionChange) {
   const roundToTwoDecimal = (/** @type {number} */ num) =>
@@ -506,7 +506,7 @@ function computeImmediateCharge(subscriptionChange) {
     tax = roundToTwoDecimal(tax + (creditInvoice.tax ?? 0))
     discount = roundToTwoDecimal(discount + (creditInvoice.discount ?? 0))
   }
-  return new RecurlyImmediateCharge({
+  return new PaymentProviderImmediateCharge({
     subtotal,
     total,
     tax,
@@ -540,41 +540,41 @@ function paymentMethodFromApi(billingInfo) {
 }
 
 /**
- * Build a RecurlyAddOn from Recurly API data
+ * Build a PaymentProviderAddOn from Recurly API data
  *
  * @param {recurly.AddOn} addOn
- * @return {RecurlyAddOn}
+ * @return {PaymentProviderAddOn}
  */
 function addOnFromApi(addOn) {
   if (addOn.code == null || addOn.name == null) {
     throw new OError('Invalid Recurly add-on', { addOn })
   }
-  return new RecurlyAddOn({
+  return new PaymentProviderAddOn({
     code: addOn.code,
     name: addOn.name,
   })
 }
 
 /**
- * Build a RecurlyPlan from Recurly API data
+ * Build a PaymentProviderPlan from Recurly API data
  *
  * @param {recurly.Plan} plan
- * @return {RecurlyPlan}
+ * @return {PaymentProviderPlan}
  */
 function planFromApi(plan) {
   if (plan.code == null || plan.name == null) {
     throw new OError('Invalid Recurly add-on', { plan })
   }
-  return new RecurlyPlan({
+  return new PaymentProviderPlan({
     code: plan.code,
     name: plan.name,
   })
 }
 
 /**
- * Build an API request from a RecurlySubscriptionChangeRequest
+ * Build an API request from a PaymentProviderSubscriptionChangeRequest
  *
- * @param {RecurlySubscriptionChangeRequest} changeRequest
+ * @param {PaymentProviderSubscriptionChangeRequest} changeRequest
  * @return {recurly.SubscriptionChangeCreate}
  */
 function subscriptionChangeRequestToApi(changeRequest) {

+ 1 - 1
services/web/app/src/Features/Subscription/RecurlyEventHandler.js

@@ -1,7 +1,7 @@
 const SplitTestHandler = require('../SplitTests/SplitTestHandler')
 const AnalyticsManager = require('../Analytics/AnalyticsManager')
 const SubscriptionEmailHandler = require('./SubscriptionEmailHandler')
-const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
+const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
 const { ObjectId } = require('mongodb-legacy')
 
 const INVOICE_SUBSCRIPTION_LIMIT = 10

+ 7 - 5
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -22,14 +22,14 @@ const Modules = require('../../infrastructure/Modules')
 const async = require('async')
 const HttpErrorHandler = require('../Errors/HttpErrorHandler')
 const RecurlyClient = require('./RecurlyClient')
-const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
+const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
 const PlansLocator = require('./PlansLocator')
-const RecurlyEntities = require('./RecurlyEntities')
+const PaymentProviderEntities = require('./PaymentProviderEntities')
 
 /**
  * @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview'
  * @import { SubscriptionChangePreview } from '../../../../types/subscription/subscription-change-preview'
- * @import { RecurlySubscriptionChange } from './RecurlyEntities'
+ * @import { PaymentProviderSubscriptionChange } from './PaymentProviderEntities'
  * @import { PaymentMethod } from './types'
  */
 
@@ -325,7 +325,9 @@ async function previewAddonPurchase(req, res) {
     const hasBundleViaWritefull =
       await FeaturesUpdater.promises.hasFeaturesViaWritefull(userId)
     const isAiUpgrade =
-      RecurlyEntities.subscriptionChangeIsAiAssistUpgrade(subscriptionChange)
+      PaymentProviderEntities.subscriptionChangeIsAiAssistUpgrade(
+        subscriptionChange
+      )
     if (hasBundleViaWritefull && isAiUpgrade) {
       return res.redirect(
         '/user/subscription?redirect-reason=writefull-entitled'
@@ -724,7 +726,7 @@ function getPlanNameForDisplay(planName, planCode) {
  * 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 {PaymentProviderSubscriptionChange} subscriptionChange The subscription change object coming from Recurly
  * @param {PaymentMethod} paymentMethod The payment method associated to the user
  * @return {SubscriptionChangePreview}
  */

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

@@ -7,7 +7,7 @@ const RecurlyClient = require('./RecurlyClient')
 const PlansLocator = require('./PlansLocator')
 const SubscriptionHandler = require('./SubscriptionHandler')
 const GroupPlansData = require('./GroupPlansData')
-const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./RecurlyEntities')
+const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./PaymentProviderEntities')
 const {
   ManuallyCollectedError,
   PendingChangeError,

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

@@ -14,7 +14,7 @@ const UserUpdater = require('../User/UserUpdater')
 const { NotFoundError } = require('../Errors/Errors')
 
 /**
- * @import { RecurlySubscription, RecurlySubscriptionChange } from './RecurlyEntities'
+ * @import { PaymentProviderSubscription, PaymentProviderSubscriptionChange } from './PaymentProviderEntities'
  */
 
 async function validateNoSubscriptionInRecurly(userId) {
@@ -69,7 +69,7 @@ async function createSubscription(user, subscriptionDetails, recurlyTokenIds) {
  *
  * @param {string} userId
  * @param {string} planCode
- * @return {Promise<RecurlySubscriptionChange>}
+ * @return {Promise<PaymentProviderSubscriptionChange>}
  */
 async function previewSubscriptionChange(userId, planCode) {
   const subscription = await getSubscriptionForUser(userId)
@@ -283,7 +283,7 @@ async function _updateSubscriptionFromRecurly(subscription) {
  *
  * @param {string} userId
  * @param {string} addOnCode
- * @return {Promise<RecurlySubscriptionChange>}
+ * @return {Promise<PaymentProviderSubscriptionChange>}
  */
 async function previewAddonPurchase(userId, addOnCode) {
   const subscription = await getSubscriptionForUser(userId)
@@ -353,7 +353,7 @@ async function removeAddon(userId, addOnCode) {
  * Throws a NotFoundError if the subscription can't be found
  *
  * @param {string} userId
- * @return {Promise<RecurlySubscription>}
+ * @return {Promise<PaymentProviderSubscription>}
  */
 async function getSubscriptionForUser(userId) {
   const subscription =

+ 1 - 1
services/web/app/src/Features/Subscription/SubscriptionLocator.js

@@ -5,7 +5,7 @@ const logger = require('@overleaf/logger')
 const {
   AI_ADD_ON_CODE,
   isStandaloneAiAddOnPlanCode,
-} = require('./RecurlyEntities')
+} = require('./PaymentProviderEntities')
 require('./GroupPlansData') // make sure dynamic group plans are loaded
 
 const SubscriptionLocator = {

+ 41 - 50
services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js

@@ -5,7 +5,7 @@ const PlansLocator = require('./PlansLocator')
 const {
   isStandaloneAiAddOnPlanCode,
   MEMBERS_LIMIT_ADD_ON_CODE,
-} = require('./RecurlyEntities')
+} = require('./PaymentProviderEntities')
 const SubscriptionFormatters = require('./SubscriptionFormatters')
 const SubscriptionLocator = require('./SubscriptionLocator')
 const SubscriptionUpdater = require('./SubscriptionUpdater')
@@ -138,8 +138,6 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
     },
   })
 
-  const recurlySubscription = paymentRecord && paymentRecord.subscription
-
   if (memberGroupSubscriptions == null) {
     memberGroupSubscriptions = []
   } else {
@@ -214,14 +212,6 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
     personalSubscription.plan = plan
   }
 
-  // Subscription DB object contains a recurly property, used to cache trial info
-  // on the project-list. However, this can cause the wrong template to render,
-  // if we do not have any subscription data from Recurly (recurlySubscription)
-  // TODO: Delete this workaround once recurly cache property name migration rolled out.
-  if (personalSubscription) {
-    delete personalSubscription.recurly
-  }
-
   function getPlanOnlyDisplayPrice(
     totalPlanPriceInCents,
     taxRate,
@@ -243,7 +233,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
     return formatCurrency(
       totalPlanPriceInCents -
         allAddOnsTotalPriceInCentsExceptAdditionalLicensePrice,
-      recurlySubscription.currency,
+      paymentRecord.subscription.currency,
       locale
     )
   }
@@ -257,7 +247,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
         if (totalPriceInCents > 0) {
           prev[curr.code] = formatCurrency(
             totalPriceInCents,
-            recurlySubscription.currency,
+            paymentRecord.subscription.currency,
             locale
           )
         }
@@ -267,15 +257,15 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
     }, {})
   }
 
-  if (personalSubscription && recurlySubscription) {
-    const tax = recurlySubscription.taxAmount || 0
+  if (personalSubscription && paymentRecord && paymentRecord.subscription) {
+    const tax = paymentRecord.subscription.taxAmount || 0
     // Some plans allow adding more seats than the base plan provides.
     // This is recorded as a subscription add on.
     // Note: taxAmount already includes the tax for any addon.
     let addOnPrice = 0
     let additionalLicenses = 0
-    const addOns = recurlySubscription.addOns || []
-    const taxRate = recurlySubscription.taxRate
+    const addOns = paymentRecord.subscription.addOns || []
+    const taxRate = paymentRecord.subscription.taxRate
     addOns.forEach(addOn => {
       addOnPrice += addOn.quantity * addOn.unitPrice
       if (addOn.code === plan.membersLimitAddOn) {
@@ -283,7 +273,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
       }
     })
     const totalLicenses = (plan.membersLimit || 0) + additionalLicenses
-    personalSubscription.recurly = {
+    personalSubscription.payment = {
       taxRate,
       billingDetailsLink: buildHostedLink('billing-details'),
       accountManagementLink: buildHostedLink('account-management'),
@@ -291,25 +281,26 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
       addOns,
       totalLicenses,
       nextPaymentDueAt: SubscriptionFormatters.formatDateTime(
-        recurlySubscription.periodEnd
+        paymentRecord.subscription.periodEnd
       ),
       nextPaymentDueDate: SubscriptionFormatters.formatDate(
-        recurlySubscription.periodEnd
+        paymentRecord.subscription.periodEnd
       ),
-      currency: recurlySubscription.currency,
-      state: recurlySubscription.state,
+      currency: paymentRecord.subscription.currency,
+      state: paymentRecord.subscription.state,
       trialEndsAtFormatted: SubscriptionFormatters.formatDateTime(
-        recurlySubscription.trialPeriodEnd
+        paymentRecord.subscription.trialPeriodEnd
       ),
-      trialEndsAt: recurlySubscription.trialPeriodEnd,
+      trialEndsAt: paymentRecord.subscription.trialPeriodEnd,
       activeCoupons: paymentRecord.coupons,
       accountEmail: paymentRecord.account.email,
       hasPastDueInvoice: paymentRecord.account.hasPastDueInvoice,
-      pausedAt: recurlySubscription.pausePeriodStart,
-      remainingPauseCycles: recurlySubscription.remainingPauseCycles,
+      pausedAt: paymentRecord.subscription.pausePeriodStart,
+      remainingPauseCycles: paymentRecord.subscription.remainingPauseCycles,
     }
-    if (recurlySubscription.pendingChange) {
-      const pendingPlanCode = recurlySubscription.pendingChange.nextPlanCode
+    if (paymentRecord.subscription.pendingChange) {
+      const pendingPlanCode =
+        paymentRecord.subscription.pendingChange.nextPlanCode
       const pendingPlan = PlansLocator.findLocalPlanInSettings(pendingPlanCode)
       if (pendingPlan == null) {
         throw new Error(`No plan found for planCode '${pendingPlanCode}'`)
@@ -317,10 +308,10 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
       let pendingAdditionalLicenses = 0
       let pendingAddOnTax = 0
       let pendingAddOnPrice = 0
-      if (recurlySubscription.pendingChange.nextAddOns) {
-        const pendingRecurlyAddons =
-          recurlySubscription.pendingChange.nextAddOns
-        pendingRecurlyAddons.forEach(addOn => {
+      if (paymentRecord.subscription.pendingChange.nextAddOns) {
+        const pendingAddOns =
+          paymentRecord.subscription.pendingChange.nextAddOns
+        pendingAddOns.forEach(addOn => {
           pendingAddOnPrice += addOn.quantity * addOn.unitPrice
           if (addOn.code === pendingPlan.membersLimitAddOn) {
             pendingAdditionalLicenses += addOn.quantity
@@ -328,50 +319,50 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
         })
         // Need to calculate tax ourselves as we don't get tax amounts for pending subs
         pendingAddOnTax =
-          personalSubscription.recurly.taxRate * pendingAddOnPrice
-        pendingPlan.addOns = pendingRecurlyAddons
+          personalSubscription.payment.taxRate * pendingAddOnPrice
+        pendingPlan.addOns = pendingAddOns
       }
       const pendingSubscriptionTax =
-        personalSubscription.recurly.taxRate *
-        recurlySubscription.pendingChange.nextPlanPrice
+        personalSubscription.payment.taxRate *
+        paymentRecord.subscription.pendingChange.nextPlanPrice
       const totalPrice =
-        recurlySubscription.pendingChange.nextPlanPrice +
+        paymentRecord.subscription.pendingChange.nextPlanPrice +
         pendingAddOnPrice +
         pendingAddOnTax +
         pendingSubscriptionTax
 
-      personalSubscription.recurly.displayPrice = formatCurrency(
+      personalSubscription.payment.displayPrice = formatCurrency(
         totalPrice,
-        recurlySubscription.currency,
+        paymentRecord.subscription.currency,
         locale
       )
-      personalSubscription.recurly.planOnlyDisplayPrice =
+      personalSubscription.payment.planOnlyDisplayPrice =
         getPlanOnlyDisplayPrice(
           totalPrice,
           taxRate,
-          recurlySubscription.pendingChange.nextAddOns
+          paymentRecord.subscription.pendingChange.nextAddOns
         )
-      personalSubscription.recurly.addOnDisplayPricesWithoutAdditionalLicense =
+      personalSubscription.payment.addOnDisplayPricesWithoutAdditionalLicense =
         getAddOnDisplayPricesWithoutAdditionalLicense(
           taxRate,
-          recurlySubscription.pendingChange.nextAddOns
+          paymentRecord.subscription.pendingChange.nextAddOns
         )
       const pendingTotalLicenses =
         (pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
-      personalSubscription.recurly.pendingAdditionalLicenses =
+      personalSubscription.payment.pendingAdditionalLicenses =
         pendingAdditionalLicenses
-      personalSubscription.recurly.pendingTotalLicenses = pendingTotalLicenses
+      personalSubscription.payment.pendingTotalLicenses = pendingTotalLicenses
       personalSubscription.pendingPlan = pendingPlan
     } else {
-      const totalPrice = recurlySubscription.planPrice + addOnPrice + tax
-      personalSubscription.recurly.displayPrice = formatCurrency(
+      const totalPrice = paymentRecord.subscription.planPrice + addOnPrice + tax
+      personalSubscription.payment.displayPrice = formatCurrency(
         totalPrice,
-        recurlySubscription.currency,
+        paymentRecord.subscription.currency,
         locale
       )
-      personalSubscription.recurly.planOnlyDisplayPrice =
+      personalSubscription.payment.planOnlyDisplayPrice =
         getPlanOnlyDisplayPrice(totalPrice, taxRate, addOns)
-      personalSubscription.recurly.addOnDisplayPricesWithoutAdditionalLicense =
+      personalSubscription.payment.addOnDisplayPricesWithoutAdditionalLicense =
         getAddOnDisplayPricesWithoutAdditionalLicense(taxRate, addOns)
     }
   }

+ 4 - 1
services/web/app/src/Features/Subscription/types.ts

@@ -1,3 +1,6 @@
-import { PaypalPaymentMethod, CreditCardPaymentMethod } from './RecurlyEntities'
+import {
+  PaypalPaymentMethod,
+  CreditCardPaymentMethod,
+} from './PaymentProviderEntities'
 
 export type PaymentMethod = PaypalPaymentMethod | CreditCardPaymentMethod

+ 2 - 2
services/web/app/views/subscriptions/dashboard-react.pug

@@ -23,9 +23,9 @@ block append meta
 	meta(name="ol-showGroupDiscount", data-type="boolean", content=showGroupDiscount)
 	meta(name="ol-groupSettingsEnabledFor", data-type="json" content=groupSettingsEnabledFor)
 	meta(name="ol-user" data-type="json" content=user)
-	if (personalSubscription && personalSubscription.recurly)
+	if (personalSubscription && personalSubscription.payment)
 		meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
-		meta(name="ol-recommendedCurrency" content=personalSubscription.recurly.currency)
+		meta(name="ol-recommendedCurrency" content=personalSubscription.payment.currency)
 		meta(name="ol-groupPlans" data-type="json" content=groupPlans)
 
 block content

+ 5 - 5
services/web/frontend/js/features/subscription/components/dashboard/pause-modal.tsx

@@ -15,7 +15,7 @@ import { debugConsole } from '@/utils/debugging'
 import * as eventTracking from '../../../../infrastructure/event-tracking'
 import PauseDuck from '../../images/pause-duck.svg'
 import GenericErrorAlert from './generic-error-alert'
-import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
 
 const pauseMonthDurationOptions = [1, 2, 3]
 
@@ -35,13 +35,13 @@ export default function PauseSubscriptionModal() {
   const location = useLocation()
 
   function handleCancelSubscriptionClick() {
-    const subscription = personalSubscription as RecurlySubscription
+    const subscription = personalSubscription as PaidSubscription
     eventTracking.sendMB('subscription-page-cancel-button-click', {
       plan_code: subscription?.planCode,
       is_trial:
-        subscription?.recurly.trialEndsAtFormatted &&
-        subscription?.recurly.trialEndsAt &&
-        new Date(subscription.recurly.trialEndsAt).getTime() > Date.now(),
+        subscription?.payment.trialEndsAtFormatted &&
+        subscription?.payment.trialEndsAt &&
+        new Date(subscription.payment.trialEndsAt).getTime() > Date.now(),
     })
     setShowCancellation(true)
   }

+ 2 - 2
services/web/frontend/js/features/subscription/components/dashboard/personal-subscription-recurly-sync-email.tsx

@@ -18,9 +18,9 @@ function PersonalSubscriptionRecurlySyncEmail() {
     runAsync(postJSON('/user/subscription/account/email'))
   }
 
-  if (!personalSubscription || !('recurly' in personalSubscription)) return null
+  if (!personalSubscription || !('payment' in personalSubscription)) return null
 
-  const recurlyEmail = personalSubscription.recurly.accountEmail
+  const recurlyEmail = personalSubscription.payment.accountEmail
 
   if (!userEmail || recurlyEmail === userEmail) return null
 

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

@@ -1,5 +1,5 @@
 import { Trans, useTranslation } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
 import { PausedSubscription } from './states/active/paused'
 import { ActiveSubscriptionNew } from '@/features/subscription/components/dashboard/states/active/active-new'
 import { CanceledSubscription } from './states/canceled'
@@ -11,7 +11,7 @@ import OLNotification from '@/features/ui/components/ol/ol-notification'
 function PastDueSubscriptionAlert({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
   return (
@@ -21,7 +21,7 @@ function PastDueSubscriptionAlert({
         <>
           {t('account_has_past_due_invoice_change_plan_warning')}{' '}
           <a
-            href={subscription.recurly.accountManagementLink}
+            href={subscription.payment.accountManagementLink}
             target="_blank"
             rel="noreferrer noopener"
           >
@@ -57,10 +57,10 @@ function RedirectAlerts() {
 function PersonalSubscriptionStates({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
-  const state = subscription?.recurly.state
+  const state = subscription?.payment.state
 
   if (state === 'active') {
     // This version handles subscriptions with and without addons
@@ -83,7 +83,7 @@ function PersonalSubscription() {
 
   if (!personalSubscription) return null
 
-  if (!('recurly' in personalSubscription)) {
+  if (!('payment' in personalSubscription)) {
     return (
       <p>
         <Trans
@@ -97,11 +97,11 @@ function PersonalSubscription() {
   return (
     <>
       <RedirectAlerts />
-      {personalSubscription.recurly.hasPastDueInvoice && (
+      {personalSubscription.payment.hasPastDueInvoice && (
         <PastDueSubscriptionAlert subscription={personalSubscription} />
       )}
       <PersonalSubscriptionStates
-        subscription={personalSubscription as RecurlySubscription}
+        subscription={personalSubscription as PaidSubscription}
       />
       {recurlyLoadError && (
         <OLNotification

+ 26 - 26
services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx

@@ -1,7 +1,7 @@
 import { useTranslation, Trans } from 'react-i18next'
 import { PriceExceptions } from '../../../shared/price-exceptions'
 import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import { CancelSubscriptionButton } from './cancel-subscription-button'
 import { CancelSubscription } from './cancel-plan/cancel-subscription'
 import { TrialEnding } from './trial-ending'
@@ -33,7 +33,7 @@ import Notification from '@/shared/components/notification'
 export function ActiveSubscriptionNew({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
   const {
@@ -77,14 +77,14 @@ export function ActiveSubscriptionNew({
     }
   }
   const hasPendingPause = Boolean(
-    subscription.recurly.state === 'active' &&
-      subscription.recurly.remainingPauseCycles &&
-      subscription.recurly.remainingPauseCycles > 0
+    subscription.payment.state === 'active' &&
+      subscription.payment.remainingPauseCycles &&
+      subscription.payment.remainingPauseCycles > 0
   )
 
   const isLegacyPlan =
-    subscription.recurly.totalLicenses !==
-    subscription.recurly.additionalLicenses
+    subscription.payment.totalLicenses !==
+    subscription.payment.additionalLicenses
 
   return (
     <>
@@ -103,7 +103,7 @@ export function ActiveSubscriptionNew({
         {subscription.plan.annual ? (
           <Trans
             i18nKey="billed_annually_at"
-            values={{ price: subscription.recurly.displayPrice }}
+            values={{ price: subscription.payment.displayPrice }}
             shouldUnescape
             tOptions={{ interpolation: { escapeValue: true } }}
             components={[
@@ -116,7 +116,7 @@ export function ActiveSubscriptionNew({
         ) : (
           <Trans
             i18nKey="billed_monthly_at"
-            values={{ price: subscription.recurly.displayPrice }}
+            values={{ price: subscription.payment.displayPrice }}
             shouldUnescape
             tOptions={{ interpolation: { escapeValue: true } }}
             components={[
@@ -131,7 +131,7 @@ export function ActiveSubscriptionNew({
       <p className="mb-1">
         <Trans
           i18nKey="renews_on"
-          values={{ date: subscription.recurly.nextPaymentDueDate }}
+          values={{ date: subscription.payment.nextPaymentDueDate }}
           shouldUnescape
           tOptions={{ interpolation: { escapeValue: true } }}
           components={[<strong />]} // eslint-disable-line react/jsx-key
@@ -139,7 +139,7 @@ export function ActiveSubscriptionNew({
       </p>
       <div>
         <a
-          href={subscription.recurly.accountManagementLink}
+          href={subscription.payment.accountManagementLink}
           target="_blank"
           rel="noreferrer noopener"
           className="me-2"
@@ -147,7 +147,7 @@ export function ActiveSubscriptionNew({
           {t('view_invoices')}
         </a>
         <a
-          href={subscription.recurly.billingDetailsLink}
+          href={subscription.payment.billingDetailsLink}
           target="_blank"
           rel="noreferrer noopener"
         >
@@ -171,21 +171,21 @@ export function ActiveSubscriptionNew({
         subscription.pendingPlan.name !== subscription.plan.name && (
           <p className="mb-1">{t('want_change_to_apply_before_plan_end')}</p>
         )}
-      {isInFreeTrial(subscription.recurly.trialEndsAt) &&
-        subscription.recurly.trialEndsAtFormatted && (
+      {isInFreeTrial(subscription.payment.trialEndsAt) &&
+        subscription.payment.trialEndsAtFormatted && (
           <TrialEnding
-            trialEndsAtFormatted={subscription.recurly.trialEndsAtFormatted}
+            trialEndsAtFormatted={subscription.payment.trialEndsAtFormatted}
             className="mb-1"
           />
         )}
-      {subscription.recurly.totalLicenses > 0 && (
+      {subscription.payment.totalLicenses > 0 && (
         <p className="mb-1">
-          {isLegacyPlan && subscription.recurly.additionalLicenses > 0 ? (
+          {isLegacyPlan && subscription.payment.additionalLicenses > 0 ? (
             <Trans
               i18nKey="plus_x_additional_licenses_for_a_total_of_y_licenses"
               values={{
-                count: subscription.recurly.totalLicenses,
-                additionalLicenses: subscription.recurly.additionalLicenses,
+                count: subscription.payment.totalLicenses,
+                additionalLicenses: subscription.payment.additionalLicenses,
               }}
               shouldUnescape
               tOptions={{ interpolation: { escapeValue: true } }}
@@ -194,7 +194,7 @@ export function ActiveSubscriptionNew({
           ) : (
             <Trans
               i18nKey="supports_up_to_x_licenses"
-              values={{ count: subscription.recurly.totalLicenses }}
+              values={{ count: subscription.payment.totalLicenses }}
               shouldUnescape
               tOptions={{ interpolation: { escapeValue: true } }}
               components={[<strong />]} // eslint-disable-line react/jsx-key
@@ -209,7 +209,7 @@ export function ActiveSubscriptionNew({
               i18nKey="your_subscription_will_pause_on"
               values={{
                 planName: subscription.plan.name,
-                pauseDate: subscription.recurly.nextPaymentDueAt,
+                pauseDate: subscription.payment.nextPaymentDueAt,
                 reactivationDate: getFormattedRenewalDate(),
               }}
               shouldUnescape
@@ -227,10 +227,10 @@ export function ActiveSubscriptionNew({
         <p className="mb-1">
           {subscription.plan.annual
             ? t('x_price_per_year', {
-                price: subscription.recurly.planOnlyDisplayPrice,
+                price: subscription.payment.planOnlyDisplayPrice,
               })
             : t('x_price_per_month', {
-                price: subscription.recurly.planOnlyDisplayPrice,
+                price: subscription.payment.planOnlyDisplayPrice,
               })}
         </p>
       )}
@@ -261,7 +261,7 @@ export function ActiveSubscriptionNew({
 }
 
 type PlanActionsProps = {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
   onStandalonePlan: boolean
   handlePlanChange: () => void
   hasPendingPause: boolean
@@ -301,7 +301,7 @@ function PlanActions({
         <FlexibleGroupLicensingActions subscription={subscription} />
       ) : (
         <>
-          {!hasPendingPause && !subscription.recurly.hasPastDueInvoice && (
+          {!hasPendingPause && !subscription.payment.hasPastDueInvoice && (
             <OLButton variant="secondary" onClick={handlePlanChange}>
               {t('change_plan')}
             </OLButton>
@@ -334,7 +334,7 @@ function PlanActions({
 function FlexibleGroupLicensingActions({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
 

+ 16 - 16
services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx

@@ -1,7 +1,7 @@
 import { useTranslation, Trans } from 'react-i18next'
 import { PriceExceptions } from '../../../shared/price-exceptions'
 import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import { CancelSubscriptionButton } from './cancel-subscription-button'
 import { CancelSubscription } from './cancel-plan/cancel-subscription'
 import { PendingPlanChange } from './pending-plan-change'
@@ -27,7 +27,7 @@ import LoadingSpinner from '@/shared/components/loading-spinner'
 export function ActiveSubscription({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
   const {
@@ -46,9 +46,9 @@ export function ActiveSubscription({
   if (showCancellation) return <CancelSubscription />
 
   const hasPendingPause =
-    subscription.recurly.state === 'active' &&
-    subscription.recurly.remainingPauseCycles &&
-    subscription.recurly.remainingPauseCycles > 0
+    subscription.payment.state === 'active' &&
+    subscription.payment.remainingPauseCycles &&
+    subscription.payment.remainingPauseCycles > 0
 
   const handleCancelPendingPauseClick = async () => {
     try {
@@ -96,19 +96,19 @@ export function ActiveSubscription({
           </>
         )}
         {!subscription.pendingPlan &&
-          subscription.recurly.additionalLicenses > 0 && (
+          subscription.payment.additionalLicenses > 0 && (
             <>
               {' '}
               <PendingAdditionalLicenses
-                additionalLicenses={subscription.recurly.additionalLicenses}
-                totalLicenses={subscription.recurly.totalLicenses}
+                additionalLicenses={subscription.payment.additionalLicenses}
+                totalLicenses={subscription.payment.totalLicenses}
               />
             </>
           )}
         {!recurlyLoadError &&
           !subscription.groupPlan &&
           !hasPendingPause &&
-          !subscription.recurly.hasPastDueInvoice && (
+          !subscription.payment.hasPastDueInvoice && (
             <>
               {' '}
               <OLButton
@@ -128,10 +128,10 @@ export function ActiveSubscription({
       {(!subscription.pendingPlan ||
         subscription.pendingPlan.name === subscription.plan.name) &&
         subscription.plan.groupPlan && <ContactSupportToChangeGroupPlan />}
-      {isInFreeTrial(subscription.recurly.trialEndsAt) &&
-        subscription.recurly.trialEndsAtFormatted && (
+      {isInFreeTrial(subscription.payment.trialEndsAt) &&
+        subscription.payment.trialEndsAtFormatted && (
           <TrialEnding
-            trialEndsAtFormatted={subscription.recurly.trialEndsAtFormatted}
+            trialEndsAtFormatted={subscription.payment.trialEndsAtFormatted}
           />
         )}
 
@@ -142,7 +142,7 @@ export function ActiveSubscription({
               i18nKey="your_subscription_will_pause_on"
               values={{
                 planName: subscription.plan.name,
-                pauseDate: subscription.recurly.nextPaymentDueAt,
+                pauseDate: subscription.payment.nextPaymentDueAt,
                 reactivationDate: getFormattedRenewalDate(),
               }}
               shouldUnescape
@@ -174,7 +174,7 @@ export function ActiveSubscription({
         <Trans
           i18nKey="next_payment_of_x_collectected_on_y"
           values={{
-            paymentAmmount: subscription.recurly.displayPrice,
+            paymentAmmount: subscription.payment.displayPrice,
             collectionDate: getFormattedRenewalDate(),
           }}
           shouldUnescape
@@ -192,7 +192,7 @@ export function ActiveSubscription({
       <PriceExceptions subscription={subscription} />
       <p className="d-inline-flex flex-wrap gap-1">
         <a
-          href={subscription.recurly.billingDetailsLink}
+          href={subscription.payment.billingDetailsLink}
           target="_blank"
           rel="noreferrer noopener"
           className="btn btn-secondary-info btn-secondary"
@@ -200,7 +200,7 @@ export function ActiveSubscription({
           {t('update_your_billing_details')}
         </a>{' '}
         <a
-          href={subscription.recurly.accountManagementLink}
+          href={subscription.payment.accountManagementLink}
           target="_blank"
           rel="noreferrer noopener"
           className="btn btn-secondary-info btn-secondary"

+ 5 - 5
services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx

@@ -9,11 +9,11 @@ import {
   AI_STANDALONE_PLAN_CODE,
 } from '@/features/subscription/data/add-on-codes'
 import sparkle from '@/shared/svgs/sparkle.svg'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import { LICENSE_ADD_ON } from '@/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details'
 
 type AddOnsProps = {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
   onStandalonePlan: boolean
   handleCancelClick: (code: string) => void
 }
@@ -106,9 +106,9 @@ function AddOns({
   const { t } = useTranslation()
   const addOnsDisplayPrices = onStandalonePlan
     ? {
-        [AI_STANDALONE_PLAN_CODE]: subscription.recurly.displayPrice,
+        [AI_STANDALONE_PLAN_CODE]: subscription.payment.displayPrice,
       }
-    : subscription.recurly.addOnDisplayPricesWithoutAdditionalLicense
+    : subscription.payment.addOnDisplayPricesWithoutAdditionalLicense
   const addOnsToDisplay = onStandalonePlan
     ? [{ addOnCode: AI_STANDALONE_PLAN_CODE }]
     : subscription.addOns?.filter(addOn => addOn.addOnCode !== LICENSE_ADD_ON)
@@ -130,7 +130,7 @@ function AddOns({
               )
             }
             displayPrice={addOnsDisplayPrices[addOn.addOnCode]}
-            nextBillingDate={subscription.recurly.nextPaymentDueDate}
+            nextBillingDate={subscription.payment.nextPaymentDueDate}
           />
         ))
       ) : (

+ 4 - 4
services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx

@@ -155,14 +155,14 @@ export function CancelSubscription() {
     isSuccessSecondaryAction ||
     isSuccessCancel
 
-  if (!personalSubscription || !('recurly' in personalSubscription)) return null
+  if (!personalSubscription || !('payment' in personalSubscription)) return null
 
   const showDowngrade = showDowngradeOption(
     personalSubscription.plan.planCode,
     personalSubscription.plan.groupPlan,
-    personalSubscription.recurly.trialEndsAt,
-    personalSubscription.recurly.pausedAt,
-    personalSubscription.recurly.remainingPauseCycles
+    personalSubscription.payment.trialEndsAt,
+    personalSubscription.payment.pausedAt,
+    personalSubscription.payment.remainingPauseCycles
   )
   const planToDowngradeTo = plans.find(
     plan => plan.planCode === planCodeToDowngradeTo

+ 9 - 9
services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx

@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
 import * as eventTracking from '../../../../../../infrastructure/event-tracking'
 import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
 import OLButton from '@/features/ui/components/ol/ol-button'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import { useFeatureFlag } from '@/shared/context/split-test-context'
 
 export function CancelSubscriptionButton() {
@@ -14,16 +14,16 @@ export function CancelSubscriptionButton() {
     setShowCancellation,
   } = useSubscriptionDashboardContext()
 
-  const subscription = personalSubscription as RecurlySubscription
+  const subscription = personalSubscription as PaidSubscription
   const isInTrial =
-    subscription?.recurly.trialEndsAtFormatted &&
-    subscription?.recurly.trialEndsAt &&
-    new Date(subscription.recurly.trialEndsAt).getTime() > Date.now()
+    subscription?.payment.trialEndsAtFormatted &&
+    subscription?.payment.trialEndsAt &&
+    new Date(subscription.payment.trialEndsAt).getTime() > Date.now()
   const hasPendingOrActivePause =
-    subscription.recurly.state === 'paused' ||
-    (subscription.recurly.state === 'active' &&
-      subscription.recurly.remainingPauseCycles &&
-      subscription.recurly.remainingPauseCycles > 0)
+    subscription.payment.state === 'paused' ||
+    (subscription.payment.state === 'active' &&
+      subscription.payment.remainingPauseCycles &&
+      subscription.payment.remainingPauseCycles > 0)
   const planIsEligibleForPause =
     !subscription.pendingPlan &&
     !subscription.groupPlan &&

+ 5 - 5
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx

@@ -3,17 +3,17 @@ import { useSubscriptionDashboardContext } from '../../../../../context/subscrip
 import OLButton from '@/features/ui/components/ol/ol-button'
 import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import isInFreeTrial from '../../../../../util/is-in-free-trial'
-import { RecurlySubscription } from '../../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../../types/subscription/dashboard/subscription'
 
 export function ChangeToGroupPlan() {
   const { t } = useTranslation()
   const { handleOpenModal, personalSubscription } =
     useSubscriptionDashboardContext()
 
-  // TODO: Better way to get RecurlySubscription/trialEndsAt
+  // TODO: Better way to get PaidSubscription/trialEndsAt
   const subscription =
-    personalSubscription && 'recurly' in personalSubscription
-      ? (personalSubscription as RecurlySubscription)
+    personalSubscription && 'payment' in personalSubscription
+      ? (personalSubscription as PaidSubscription)
       : null
 
   const handleClick = () => {
@@ -25,7 +25,7 @@ export function ChangeToGroupPlan() {
       <h2 style={{ marginTop: 0 }}>{t('looking_multiple_licenses')}</h2>
       <p style={{ margin: 0 }}>{t('reduce_costs_group_licenses')}</p>
       <br />
-      {isInFreeTrial(subscription?.recurly?.trialEndsAt) ? (
+      {isInFreeTrial(subscription?.payment?.trialEndsAt) ? (
         <>
           <OLTooltip
             id="disabled-change-to-group-plan"

+ 2 - 2
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx

@@ -1,6 +1,6 @@
 import { useEffect, useState } from 'react'
 import { useTranslation, Trans } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../../../types/subscription/dashboard/subscription'
 import { PriceForDisplayData } from '../../../../../../../../../../types/subscription/plan'
 import { postJSON } from '../../../../../../../../infrastructure/fetch-json'
 import getMeta from '../../../../../../../../utils/meta'
@@ -115,7 +115,7 @@ export function ChangeToGroupModal() {
     useContactUsModal({ autofillProjectUrl: false })
   const groupPlans = getMeta('ol-groupPlans')
   const showGroupDiscount = getMeta('ol-showGroupDiscount')
-  const personalSubscription = getMeta('ol-subscription') as RecurlySubscription
+  const personalSubscription = getMeta('ol-subscription') as PaidSubscription
   const [error, setError] = useState(false)
   const [inflight, setInflight] = useState(false)
   const location = useLocation()

+ 3 - 3
services/web/frontend/js/features/subscription/components/dashboard/states/active/confirm-unpause-modal.tsx

@@ -12,7 +12,7 @@ import OLButton from '@/features/ui/components/ol/ol-button'
 import { postJSON } from '@/infrastructure/fetch-json'
 import { useLocation } from '@/shared/hooks/use-location'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 
 export function ConfirmUnpauseSubscriptionModal() {
   const modalId: SubscriptionDashModalIds = 'unpause-subscription'
@@ -22,7 +22,7 @@ export function ConfirmUnpauseSubscriptionModal() {
   const { handleCloseModal, modalIdShown, personalSubscription } =
     useSubscriptionDashboardContext()
   const location = useLocation()
-  const subscription = personalSubscription as RecurlySubscription
+  const subscription = personalSubscription as PaidSubscription
 
   async function handleConfirmUnpause() {
     setError(false)
@@ -70,7 +70,7 @@ export function ConfirmUnpauseSubscriptionModal() {
           <Trans
             i18nKey="lets_get_those_premium_features"
             values={{
-              paymentAmount: subscription.recurly.displayPrice,
+              paymentAmount: subscription.payment.displayPrice,
             }}
             shouldUnescape
             tOptions={{ interpolation: { escapeValue: true } }}

+ 3 - 3
services/web/frontend/js/features/subscription/components/dashboard/states/active/flash-message.tsx

@@ -1,7 +1,7 @@
 import { useSubscriptionDashboardContext } from '@/features/subscription/context/subscription-dashboard-context'
 import Notification from '@/shared/components/notification'
 import { Trans, useTranslation } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import { useEffect, useState } from 'react'
 import { useLocation } from '@/shared/hooks/use-location'
 
@@ -17,7 +17,7 @@ export function FlashMessage() {
       'flash'
     ) as FlashMessageName
   )
-  const subscription = personalSubscription as RecurlySubscription
+  const subscription = personalSubscription as PaidSubscription
   useEffect(() => {
     // clear any flash message IDs so they only show once
     if (location.toString()) {
@@ -36,7 +36,7 @@ export function FlashMessage() {
             <Trans
               i18nKey="your_subscription_will_pause_on_short"
               values={{
-                pauseDate: subscription.recurly.nextPaymentDueAt,
+                pauseDate: subscription.payment.nextPaymentDueAt,
               }}
               shouldUnescape
               tOptions={{ interpolation: { escapeValue: true } }}

+ 4 - 4
services/web/frontend/js/features/subscription/components/dashboard/states/active/paused.tsx

@@ -1,6 +1,6 @@
 import { useTranslation, Trans } from 'react-i18next'
 import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import { CancelSubscriptionButton } from './cancel-subscription-button'
 import { CancelSubscription } from './cancel-plan/cancel-subscription'
 import { ChangePlanModal } from './change-plan/modals/change-plan-modal'
@@ -14,7 +14,7 @@ import { ConfirmUnpauseSubscriptionModal } from './confirm-unpause-modal'
 export function PausedSubscription({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
   const {
@@ -67,7 +67,7 @@ export function PausedSubscription({
 
       <p className="d-inline-flex flex-wrap gap-1">
         <a
-          href={subscription.recurly.billingDetailsLink}
+          href={subscription.payment.billingDetailsLink}
           target="_blank"
           rel="noreferrer noopener"
           className="btn btn-secondary-info btn-secondary"
@@ -75,7 +75,7 @@ export function PausedSubscription({
           {t('update_your_billing_details')}
         </a>{' '}
         <a
-          href={subscription.recurly.accountManagementLink}
+          href={subscription.payment.accountManagementLink}
           target="_blank"
           rel="noreferrer noopener"
           className="btn btn-secondary-info btn-secondary"

+ 9 - 9
services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx

@@ -1,16 +1,16 @@
 import { Trans } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
-import { PendingRecurlyPlan } from '../../../../../../../../types/subscription/plan'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PendingPaymentProviderPlan } from '../../../../../../../../types/subscription/plan'
 import { AI_ADD_ON_CODE, ADD_ON_NAME } from '../../../../data/add-on-codes'
 
 export function PendingPlanChange({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   if (!subscription.pendingPlan) return null
 
-  const pendingPlan = subscription.pendingPlan as PendingRecurlyPlan
+  const pendingPlan = subscription.pendingPlan as PendingPaymentProviderPlan
 
   const hasAiAddon = subscription.addOns?.some(
     addOn => addOn.addOnCode === AI_ADD_ON_CODE
@@ -21,9 +21,9 @@ export function PendingPlanChange({
     !pendingPlan.addOns?.some(addOn => addOn.code === AI_ADD_ON_CODE)
 
   const pendingAdditionalLicenses =
-    (subscription.recurly.pendingAdditionalLicenses &&
-      subscription.recurly.pendingAdditionalLicenses > 0) ||
-    subscription.recurly.additionalLicenses > 0
+    (subscription.payment.pendingAdditionalLicenses &&
+      subscription.payment.pendingAdditionalLicenses > 0) ||
+    subscription.payment.additionalLicenses > 0
 
   return (
     <>
@@ -49,8 +49,8 @@ export function PendingPlanChange({
             i18nKey="pending_additional_licenses"
             values={{
               pendingAdditionalLicenses:
-                subscription.recurly.pendingAdditionalLicenses,
-              pendingTotalLicenses: subscription.recurly.pendingTotalLicenses,
+                subscription.payment.pendingAdditionalLicenses,
+              pendingTotalLicenses: subscription.payment.pendingTotalLicenses,
             }}
             shouldUnescape
             tOptions={{ interpolation: { escapeValue: true } }}

+ 7 - 7
services/web/frontend/js/features/subscription/components/dashboard/states/active/subscription-remainder.tsx

@@ -1,8 +1,8 @@
 import { Trans } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 
 type SubscriptionRemainderProps = {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
   hideTime?: boolean
 }
 
@@ -11,13 +11,13 @@ function SubscriptionRemainder({
   hideTime,
 }: SubscriptionRemainderProps) {
   const stillInATrial =
-    subscription.recurly.trialEndsAtFormatted &&
-    subscription.recurly.trialEndsAt &&
-    new Date(subscription.recurly.trialEndsAt).getTime() > Date.now()
+    subscription.payment.trialEndsAtFormatted &&
+    subscription.payment.trialEndsAt &&
+    new Date(subscription.payment.trialEndsAt).getTime() > Date.now()
 
   const terminationDate = hideTime
-    ? subscription.recurly.nextPaymentDueDate
-    : subscription.recurly.nextPaymentDueAt
+    ? subscription.payment.nextPaymentDueDate
+    : subscription.payment.nextPaymentDueAt
   return stillInATrial ? (
     <Trans
       i18nKey="subscription_will_remain_active_until_end_of_trial_period_x"

+ 4 - 4
services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx

@@ -1,12 +1,12 @@
 import { useTranslation, Trans } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../types/subscription/dashboard/subscription'
 import ReactivateSubscription from '../reactivate-subscription'
 import OLButton from '@/features/ui/components/ol/ol-button'
 
 export function CanceledSubscription({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
 
@@ -30,7 +30,7 @@ export function CanceledSubscription({
         <Trans
           i18nKey="subscription_canceled_and_terminate_on_x"
           values={{
-            terminateDate: subscription.recurly.nextPaymentDueAt,
+            terminateDate: subscription.payment.nextPaymentDueAt,
           }}
           shouldUnescape
           tOptions={{ interpolation: { escapeValue: true } }}
@@ -42,7 +42,7 @@ export function CanceledSubscription({
       </p>
       <p>
         <OLButton
-          href={subscription.recurly.accountManagementLink}
+          href={subscription.payment.accountManagementLink}
           target="_blank"
           variant="secondary"
           rel="noopener noreferrer"

+ 3 - 3
services/web/frontend/js/features/subscription/components/dashboard/states/expired.tsx

@@ -1,11 +1,11 @@
 import { useTranslation } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../types/subscription/dashboard/subscription'
 import OLButton from '@/features/ui/components/ol/ol-button'
 
 export function ExpiredSubscription({
   subscription,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }) {
   const { t } = useTranslation()
 
@@ -14,7 +14,7 @@ export function ExpiredSubscription({
       <p>{t('your_subscription_has_expired')}</p>
       <p>
         <OLButton
-          href={subscription.recurly.accountManagementLink}
+          href={subscription.payment.accountManagementLink}
           target="_blank"
           rel="noreferrer noopener"
           variant="secondary"

+ 3 - 3
services/web/frontend/js/features/subscription/components/shared/price-exceptions.tsx

@@ -1,13 +1,13 @@
 import { useTranslation } from 'react-i18next'
-import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
 
 type PriceExceptionsProps = {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
 }
 
 export function PriceExceptions({ subscription }: PriceExceptionsProps) {
   const { t } = useTranslation()
-  const { activeCoupons } = subscription.recurly
+  const { activeCoupons } = subscription.payment
 
   return (
     <>

+ 6 - 6
services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx

@@ -12,7 +12,7 @@ import {
   ADD_ON_NAME,
   isStandaloneAiPlanCode,
 } from '../../data/add-on-codes'
-import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
 
 function SuccessfulSubscription() {
   const { t } = useTranslation()
@@ -21,7 +21,7 @@ function SuccessfulSubscription() {
   const postCheckoutRedirect = getMeta('ol-postCheckoutRedirect')
   const { appName, adminEmail } = getMeta('ol-ExposedSettings')
 
-  if (!subscription || !('recurly' in subscription)) return null
+  if (!subscription || !('payment' in subscription)) return null
 
   const onAiStandalonePlan = isStandaloneAiPlanCode(subscription.planCode)
 
@@ -37,15 +37,15 @@ function SuccessfulSubscription() {
               type="success"
               content={
                 <>
-                  {subscription.recurly.trialEndsAt && (
+                  {subscription.payment.trialEndsAt && (
                     <>
                       <p>
                         <Trans
                           i18nKey="next_payment_of_x_collectected_on_y"
                           values={{
-                            paymentAmmount: subscription.recurly.displayPrice,
+                            paymentAmmount: subscription.payment.displayPrice,
                             collectionDate:
-                              subscription.recurly.nextPaymentDueAt,
+                              subscription.payment.nextPaymentDueAt,
                           }}
                           shouldUnescape
                           tOptions={{ interpolation: { escapeValue: true } }}
@@ -126,7 +126,7 @@ function ThankYouSection({
   subscription,
   onAiStandalonePlan,
 }: {
-  subscription: RecurlySubscription
+  subscription: PaidSubscription
   onAiStandalonePlan: boolean
 }) {
   const { t } = useTranslation()

+ 12 - 12
services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx

@@ -12,7 +12,7 @@ import {
   CustomSubscription,
   ManagedGroupSubscription,
   MemberGroupSubscription,
-  RecurlySubscription,
+  PaidSubscription,
 } from '../../../../../types/subscription/dashboard/subscription'
 import {
   Plan,
@@ -52,7 +52,7 @@ type SubscriptionDashboardContextValue = {
   managedPublishers: Publisher[]
   updateManagedInstitution: (institution: ManagedInstitution) => void
   modalIdShown?: SubscriptionDashModalIds
-  personalSubscription?: RecurlySubscription | CustomSubscription
+  personalSubscription?: PaidSubscription | CustomSubscription
   hasSubscription: boolean
   plans: Plan[]
   planCodeToChangeTo?: string
@@ -136,21 +136,21 @@ export function SubscriptionDashboardProvider({
   )
 
   const hasValidActiveSubscription = Boolean(
-    ['active', 'canceled'].includes(personalSubscription?.recurly?.state) ||
+    ['active', 'canceled'].includes(personalSubscription?.payment?.state) ||
       institutionMemberships?.length > 0 ||
       memberGroupSubscriptions?.length > 0
   )
 
   const getFormattedRenewalDate = useCallback(() => {
     if (
-      !personalSubscription.recurly.pausedAt ||
-      !personalSubscription.recurly.remainingPauseCycles
+      !personalSubscription.payment.pausedAt ||
+      !personalSubscription.payment.remainingPauseCycles
     ) {
-      return personalSubscription.recurly.nextPaymentDueAt
+      return personalSubscription.payment.nextPaymentDueAt
     }
-    const pausedDate = new Date(personalSubscription.recurly.pausedAt)
+    const pausedDate = new Date(personalSubscription.payment.pausedAt)
     pausedDate.setMonth(
-      pausedDate.getMonth() + personalSubscription.recurly.remainingPauseCycles
+      pausedDate.getMonth() + personalSubscription.payment.remainingPauseCycles
     )
     return formatTime(pausedDate, 'MMMM Do, YYYY')
   }, [personalSubscription])
@@ -167,9 +167,9 @@ export function SubscriptionDashboardProvider({
     if (
       isRecurlyLoaded() &&
       plansWithoutDisplayPrice &&
-      personalSubscription?.recurly
+      personalSubscription?.payment
     ) {
-      const { currency, taxRate } = personalSubscription.recurly
+      const { currency, taxRate } = personalSubscription.payment
       const fetchPlansDisplayPrices = async () => {
         for (const plan of plansWithoutDisplayPrice) {
           try {
@@ -203,11 +203,11 @@ export function SubscriptionDashboardProvider({
       groupPlanToChangeToCode &&
       groupPlanToChangeToSize &&
       groupPlanToChangeToUsage &&
-      personalSubscription?.recurly
+      personalSubscription?.payment
     ) {
       setQueryingGroupPlanToChangeToPrice(true)
 
-      const { currency, taxRate } = personalSubscription.recurly
+      const { currency, taxRate } = personalSubscription.payment
       const fetchGroupDisplayPrice = async () => {
         setGroupPlanToChangeToPriceError(false)
         let priceData

+ 4 - 4
services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx

@@ -83,7 +83,7 @@ describe('<PersonalSubscription />', function () {
         'Your subscription has been canceled and will terminate on',
         { exact: false }
       )
-      screen.getByText(canceledSubscription.recurly!.nextPaymentDueAt, {
+      screen.getByText(canceledSubscription.payment!.nextPaymentDueAt, {
         exact: false,
       })
 
@@ -134,7 +134,7 @@ describe('<PersonalSubscription />', function () {
         {},
         JSON.parse(JSON.stringify(annualActiveSubscription))
       )
-      withStateDeleted.recurly.state = undefined
+      withStateDeleted.payment.state = undefined
       renderWithSubscriptionDashContext(<PersonalSubscription />, {
         metaTags: [{ name: 'ol-subscription', value: withStateDeleted }],
       })
@@ -194,7 +194,7 @@ describe('<PersonalSubscription />', function () {
     })
   })
 
-  it('shows different recurly email address section', async function () {
+  it('shows different payment email address section', async function () {
     fetchMock.post('/user/subscription/account/email', 200)
     const usersEmail = 'foo@example.com'
     renderWithSubscriptionDashContext(<PersonalSubscription />, {
@@ -208,7 +208,7 @@ describe('<PersonalSubscription />', function () {
       /your billing email address is currently/i
     ).textContent
     expect(billingText).to.contain(
-      `Your billing email address is currently ${annualActiveSubscription.recurly.accountEmail}.` +
+      `Your billing email address is currently ${annualActiveSubscription.payment.accountEmail}.` +
         ` If needed you can update your billing address to ${usersEmail}`
     )
 

+ 18 - 18
services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx

@@ -1,7 +1,7 @@
 import { expect } from 'chai'
 import { fireEvent, screen, waitFor } from '@testing-library/react'
 import * as eventTracking from '@/infrastructure/event-tracking'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import {
   annualActiveSubscription,
   groupActiveSubscription,
@@ -36,7 +36,7 @@ describe('<ActiveSubscription />', function () {
     sendMBSpy.restore()
   })
 
-  function expectedInActiveSubscription(subscription: RecurlySubscription) {
+  function expectedInActiveSubscription(subscription: PaidSubscription) {
     // sentence broken up by bolding
     screen.getByText('You are currently subscribed to the', { exact: false })
     screen.getByText(subscription.plan.name, { exact: false })
@@ -45,11 +45,11 @@ describe('<ActiveSubscription />', function () {
 
     // sentence broken up by bolding
     screen.getByText('The next payment of', { exact: false })
-    screen.getByText(subscription.recurly.displayPrice, {
+    screen.getByText(subscription.payment.displayPrice, {
       exact: false,
     })
     screen.getByText('will be collected on', { exact: false })
-    const dates = screen.getAllByText(subscription.recurly.nextPaymentDueAt, {
+    const dates = screen.getAllByText(subscription.payment.nextPaymentDueAt, {
       exact: false,
     })
     expect(dates.length).to.equal(2)
@@ -110,7 +110,7 @@ describe('<ActiveSubscription />', function () {
       JSON.parse(JSON.stringify(annualActiveSubscription))
     )
 
-    activePastDueSubscription.recurly.hasPastDueInvoice = true
+    activePastDueSubscription.payment.hasPastDueInvoice = true
 
     renderActiveSubscription(activePastDueSubscription)
 
@@ -126,14 +126,14 @@ describe('<ActiveSubscription />', function () {
     })
 
     screen.getByText(
-      groupActiveSubscriptionWithPendingLicenseChange.recurly
+      groupActiveSubscriptionWithPendingLicenseChange.payment
         .pendingAdditionalLicenses!
     )
 
     screen.getByText('additional license(s) for a total of', { exact: false })
 
     screen.getByText(
-      groupActiveSubscriptionWithPendingLicenseChange.recurly
+      groupActiveSubscriptionWithPendingLicenseChange.payment
         .pendingTotalLicenses!
     )
 
@@ -146,10 +146,10 @@ describe('<ActiveSubscription />', function () {
 
   it('shows the pending license change message when plan change is not pending', function () {
     const subscription = Object.assign({}, groupActiveSubscription)
-    subscription.recurly.additionalLicenses = 4
-    subscription.recurly.totalLicenses =
-      subscription.recurly.totalLicenses +
-      subscription.recurly.additionalLicenses
+    subscription.payment.additionalLicenses = 4
+    subscription.payment.totalLicenses =
+      subscription.payment.totalLicenses +
+      subscription.payment.additionalLicenses
 
     renderActiveSubscription(subscription)
 
@@ -157,11 +157,11 @@ describe('<ActiveSubscription />', function () {
       exact: false,
     })
 
-    screen.getByText(subscription.recurly.additionalLicenses)
+    screen.getByText(subscription.payment.additionalLicenses)
 
     screen.getByText('additional license(s) for a total of', { exact: false })
 
-    screen.getByText(subscription.recurly.totalLicenses)
+    screen.getByText(subscription.payment.totalLicenses)
   })
 
   it('shows when trial ends and first payment collected and when subscription would become inactive if cancelled', function () {
@@ -169,14 +169,14 @@ describe('<ActiveSubscription />', function () {
     screen.getByText('You’re on a free trial which ends on', { exact: false })
 
     const endDate = screen.getAllByText(
-      trialSubscription.recurly.trialEndsAtFormatted!
+      trialSubscription.payment.trialEndsAtFormatted!
     )
     expect(endDate.length).to.equal(3)
   })
 
   it('shows current discounts', function () {
     const subscriptionWithActiveCoupons = cloneDeep(annualActiveSubscription)
-    subscriptionWithActiveCoupons.recurly.activeCoupons = [
+    subscriptionWithActiveCoupons.payment.activeCoupons = [
       {
         name: 'fake coupon name',
         code: 'fake-coupon',
@@ -188,7 +188,7 @@ describe('<ActiveSubscription />', function () {
       /this does not include your current discounts, which will be applied automatically before your next payment/i
     )
     screen.getByText(
-      subscriptionWithActiveCoupons.recurly.activeCoupons[0].name
+      subscriptionWithActiveCoupons.payment.activeCoupons[0].name
     )
   })
 
@@ -225,7 +225,7 @@ describe('<ActiveSubscription />', function () {
         { exact: false }
       )
       const dates = screen.getAllByText(
-        annualActiveSubscription.recurly.nextPaymentDueAt,
+        annualActiveSubscription.payment.nextPaymentDueAt,
         {
           exact: false,
         }
@@ -244,7 +244,7 @@ describe('<ActiveSubscription />', function () {
         { exact: false }
       )
       const dates = screen.getAllByText(
-        trialSubscription.recurly.trialEndsAtFormatted!
+        trialSubscription.payment.trialEndsAtFormatted!
       )
       expect(dates.length).to.equal(3)
       const button = screen.getByRole('button', {

+ 21 - 21
services/web/test/frontend/features/subscription/fixtures/subscriptions.ts

@@ -1,7 +1,7 @@
 import {
   CustomSubscription,
   GroupSubscription,
-  RecurlySubscription,
+  PaidSubscription,
 } from '../../../../../types/subscription/dashboard/subscription'
 import dateformat from 'dateformat'
 
@@ -15,7 +15,7 @@ const sevenDaysFromTodayFormatted = dateformat(
   'dS mmmm yyyy'
 )
 
-export const annualActiveSubscription: RecurlySubscription = {
+export const annualActiveSubscription: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -33,7 +33,7 @@ export const annualActiveSubscription: RecurlySubscription = {
     annual: true,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -55,7 +55,7 @@ export const annualActiveSubscription: RecurlySubscription = {
   },
 }
 
-export const annualActiveSubscriptionEuro: RecurlySubscription = {
+export const annualActiveSubscriptionEuro: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -73,7 +73,7 @@ export const annualActiveSubscriptionEuro: RecurlySubscription = {
     annual: true,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0.24,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -95,7 +95,7 @@ export const annualActiveSubscriptionEuro: RecurlySubscription = {
   },
 }
 
-export const annualActiveSubscriptionPro: RecurlySubscription = {
+export const annualActiveSubscriptionPro: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -112,7 +112,7 @@ export const annualActiveSubscriptionPro: RecurlySubscription = {
     price_in_cents: 4500,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -134,7 +134,7 @@ export const annualActiveSubscriptionPro: RecurlySubscription = {
   },
 }
 
-export const pastDueExpiredSubscription: RecurlySubscription = {
+export const pastDueExpiredSubscription: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -152,7 +152,7 @@ export const pastDueExpiredSubscription: RecurlySubscription = {
     annual: true,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -174,7 +174,7 @@ export const pastDueExpiredSubscription: RecurlySubscription = {
   },
 }
 
-export const canceledSubscription: RecurlySubscription = {
+export const canceledSubscription: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -192,7 +192,7 @@ export const canceledSubscription: RecurlySubscription = {
     annual: true,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -214,7 +214,7 @@ export const canceledSubscription: RecurlySubscription = {
   },
 }
 
-export const pendingSubscriptionChange: RecurlySubscription = {
+export const pendingSubscriptionChange: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -232,7 +232,7 @@ export const pendingSubscriptionChange: RecurlySubscription = {
     annual: true,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -283,7 +283,7 @@ export const groupActiveSubscription: GroupSubscription = {
     membersLimit: 10,
     membersLimitAddOn: 'additional-license',
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -328,7 +328,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription
       membersLimit: 10,
       membersLimitAddOn: 'additional-license',
     },
-    recurly: {
+    payment: {
       taxRate: 0,
       billingDetailsLink: '/user/subscription/recurly/billing-details',
       accountManagementLink: '/user/subscription/recurly/account-management',
@@ -362,7 +362,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription
     },
   }
 
-export const trialSubscription: RecurlySubscription = {
+export const trialSubscription: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -380,7 +380,7 @@ export const trialSubscription: RecurlySubscription = {
     featureDescription: [],
     hideFromUsers: true,
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -423,7 +423,7 @@ export const customSubscription: CustomSubscription = {
   customAccount: true,
 }
 
-export const trialCollaboratorSubscription: RecurlySubscription = {
+export const trialCollaboratorSubscription: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -441,7 +441,7 @@ export const trialCollaboratorSubscription: RecurlySubscription = {
     featureDescription: [],
     hideFromUsers: true,
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',
@@ -463,7 +463,7 @@ export const trialCollaboratorSubscription: RecurlySubscription = {
   },
 }
 
-export const monthlyActiveCollaborator: RecurlySubscription = {
+export const monthlyActiveCollaborator: PaidSubscription = {
   manager_ids: ['abc123'],
   member_ids: [],
   invited_emails: [],
@@ -480,7 +480,7 @@ export const monthlyActiveCollaborator: RecurlySubscription = {
     price_in_cents: 212300900,
     featureDescription: [],
   },
-  recurly: {
+  payment: {
     taxRate: 0,
     billingDetailsLink: '/user/subscription/recurly/billing-details',
     accountManagementLink: '/user/subscription/recurly/account-management',

+ 2 - 2
services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx

@@ -1,12 +1,12 @@
 import { ActiveSubscription } from '../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
-import { RecurlySubscription } from '../../../../../types/subscription/dashboard/subscription'
+import { PaidSubscription } from '../../../../../types/subscription/dashboard/subscription'
 import { groupPlans, plans } from '../fixtures/plans'
 import { renderWithSubscriptionDashContext } from './render-with-subscription-dash-context'
 import { MetaTag } from '@/utils/meta'
 import { CurrencyCode } from '../../../../../types/subscription/currency'
 
 export function renderActiveSubscription(
-  subscription: RecurlySubscription,
+  subscription: PaidSubscription,
   tags: MetaTag[] = [],
   currencyCode?: CurrencyCode
 ) {

+ 1 - 1
services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js

@@ -4,7 +4,7 @@ const sinon = require('sinon')
 const { ObjectId } = require('mongodb-legacy')
 const {
   AI_ADD_ON_CODE,
-} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
 const MODULE_PATH = '../../../../app/src/Features/Subscription/FeaturesUpdater'
 

+ 64 - 56
services/web/test/unit/src/Subscription/RecurlyEntitiesTest.js → services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js

@@ -5,16 +5,17 @@ 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')
+  PaymentProviderSubscriptionChangeRequest,
+  PaymentProviderSubscriptionChange,
+  PaymentProviderSubscription,
+  PaymentProviderSubscriptionAddOnUpdate,
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
-const MODULE_PATH = '../../../../app/src/Features/Subscription/RecurlyEntities'
+const MODULE_PATH =
+  '../../../../app/src/Features/Subscription/PaymentProviderEntities'
 
-describe('RecurlyEntities', function () {
-  describe('RecurlySubscription', function () {
+describe('PaymentProviderEntities', function () {
+  describe('PaymentProviderSubscription', function () {
     beforeEach(function () {
       this.Settings = {
         plans: [
@@ -26,7 +27,7 @@ describe('RecurlyEntities', function () {
         features: [],
       }
 
-      this.RecurlyEntities = SandboxedModule.require(MODULE_PATH, {
+      this.PaymentProviderEntities = SandboxedModule.require(MODULE_PATH, {
         requires: {
           '@overleaf/settings': this.Settings,
           './Errors': Errors,
@@ -36,15 +37,17 @@ describe('RecurlyEntities', function () {
 
     describe('with add-ons', function () {
       beforeEach(function () {
-        const { RecurlySubscription, RecurlySubscriptionAddOn } =
-          this.RecurlyEntities
-        this.addOn = new RecurlySubscriptionAddOn({
+        const {
+          PaymentProviderSubscription,
+          PaymentProviderSubscriptionAddOn,
+        } = this.PaymentProviderEntities
+        this.addOn = new PaymentProviderSubscriptionAddOn({
           code: 'add-on-code',
           name: 'My Add-On',
           quantity: 1,
           unitPrice: 2,
         })
-        this.subscription = new RecurlySubscription({
+        this.subscription = new PaymentProviderSubscription({
           id: 'subscription-id',
           userId: 'user-id',
           planCode: 'regular-plan',
@@ -71,11 +74,12 @@ describe('RecurlyEntities', function () {
 
       describe('getRequestForPlanChange()', function () {
         it('returns a change request for upgrades', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
+          const { PaymentProviderSubscriptionChangeRequest } =
+            this.PaymentProviderEntities
           const changeRequest =
             this.subscription.getRequestForPlanChange('premium-plan')
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               planCode: 'premium-plan',
@@ -84,11 +88,12 @@ describe('RecurlyEntities', function () {
         })
 
         it('returns a change request for downgrades', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
+          const { PaymentProviderSubscriptionChangeRequest } =
+            this.PaymentProviderEntities
           const changeRequest =
             this.subscription.getRequestForPlanChange('cheap-plan')
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'term_end',
               planCode: 'cheap-plan',
@@ -97,17 +102,18 @@ describe('RecurlyEntities', function () {
         })
 
         it('preserves the AI add-on on upgrades', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
+          const { PaymentProviderSubscriptionChangeRequest } =
+            this.PaymentProviderEntities
           this.addOn.code = AI_ADD_ON_CODE
           const changeRequest =
             this.subscription.getRequestForPlanChange('premium-plan')
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               planCode: 'premium-plan',
               addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: AI_ADD_ON_CODE,
                   quantity: 1,
                 }),
@@ -117,17 +123,18 @@ describe('RecurlyEntities', function () {
         })
 
         it('preserves the AI add-on on downgrades', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
+          const { PaymentProviderSubscriptionChangeRequest } =
+            this.PaymentProviderEntities
           this.addOn.code = AI_ADD_ON_CODE
           const changeRequest =
             this.subscription.getRequestForPlanChange('cheap-plan')
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'term_end',
               planCode: 'cheap-plan',
               addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: AI_ADD_ON_CODE,
                   quantity: 1,
                 }),
@@ -137,18 +144,19 @@ describe('RecurlyEntities', function () {
         })
 
         it('preserves the AI add-on on upgrades from the standalone AI plan', function () {
-          const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
+          const { PaymentProviderSubscriptionChangeRequest } =
+            this.PaymentProviderEntities
           this.subscription.planCode = 'assistant-annual'
           this.subscription.addOns = []
           const changeRequest =
             this.subscription.getRequestForPlanChange('cheap-plan')
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'term_end',
               planCode: 'cheap-plan',
               addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: AI_ADD_ON_CODE,
                   quantity: 1,
                 }),
@@ -161,22 +169,22 @@ describe('RecurlyEntities', function () {
       describe('getRequestForAddOnPurchase()', function () {
         it('returns a change request', function () {
           const {
-            RecurlySubscriptionChangeRequest,
-            RecurlySubscriptionAddOnUpdate,
-          } = this.RecurlyEntities
+            PaymentProviderSubscriptionChangeRequest,
+            PaymentProviderSubscriptionAddOnUpdate,
+          } = this.PaymentProviderEntities
           const changeRequest =
             this.subscription.getRequestForAddOnPurchase('another-add-on')
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: this.addOn.code,
                   quantity: this.addOn.quantity,
                   unitPrice: this.addOn.unitPrice,
                 }),
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: 'another-add-on',
                   quantity: 1,
                 }),
@@ -187,9 +195,9 @@ describe('RecurlyEntities', function () {
 
         it('returns a change request with quantity and unit price specified', function () {
           const {
-            RecurlySubscriptionChangeRequest,
-            RecurlySubscriptionAddOnUpdate,
-          } = this.RecurlyEntities
+            PaymentProviderSubscriptionChangeRequest,
+            PaymentProviderSubscriptionAddOnUpdate,
+          } = this.PaymentProviderEntities
           const quantity = 5
           const unitPrice = 10
           const changeRequest = this.subscription.getRequestForAddOnPurchase(
@@ -198,16 +206,16 @@ describe('RecurlyEntities', function () {
             unitPrice
           )
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: this.addOn.code,
                   quantity: this.addOn.quantity,
                   unitPrice: this.addOn.unitPrice,
                 }),
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: 'another-add-on',
                   quantity,
                   unitPrice,
@@ -227,20 +235,20 @@ describe('RecurlyEntities', function () {
       describe('getRequestForAddOnUpdate()', function () {
         it('returns a change request', function () {
           const {
-            RecurlySubscriptionChangeRequest,
-            RecurlySubscriptionAddOnUpdate,
-          } = this.RecurlyEntities
+            PaymentProviderSubscriptionChangeRequest,
+            PaymentProviderSubscriptionAddOnUpdate,
+          } = this.PaymentProviderEntities
           const newQuantity = 2
           const changeRequest = this.subscription.getRequestForAddOnUpdate(
             'add-on-code',
             newQuantity
           )
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               addOnUpdates: [
-                new RecurlySubscriptionAddOnUpdate({
+                new PaymentProviderSubscriptionAddOnUpdate({
                   code: this.addOn.code,
                   quantity: newQuantity,
                   unitPrice: this.addOn.unitPrice,
@@ -263,7 +271,7 @@ describe('RecurlyEntities', function () {
             this.addOn.code
           )
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'term_end',
               addOnUpdates: [],
@@ -283,13 +291,13 @@ describe('RecurlyEntities', function () {
           const changeRequest =
             this.subscription.getRequestForGroupPlanUpgrade('test_plan_code')
           const addOns = [
-            new RecurlySubscriptionAddOnUpdate({
+            new PaymentProviderSubscriptionAddOnUpdate({
               code: 'add-on-code',
               quantity: 1,
             }),
           ]
           expect(changeRequest).to.deep.equal(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               addOnUpdates: addOns,
@@ -301,8 +309,8 @@ describe('RecurlyEntities', function () {
 
       describe('without add-ons', function () {
         beforeEach(function () {
-          const { RecurlySubscription } = this.RecurlyEntities
-          this.subscription = new RecurlySubscription({
+          const { PaymentProviderSubscription } = this.PaymentProviderEntities
+          this.subscription = new PaymentProviderSubscription({
             id: 'subscription-id',
             userId: 'user-id',
             planCode: 'regular-plan',
@@ -325,17 +333,17 @@ describe('RecurlyEntities', function () {
         describe('getRequestForAddOnPurchase()', function () {
           it('returns a change request', function () {
             const {
-              RecurlySubscriptionChangeRequest,
-              RecurlySubscriptionAddOnUpdate,
-            } = this.RecurlyEntities
+              PaymentProviderSubscriptionChangeRequest,
+              PaymentProviderSubscriptionAddOnUpdate,
+            } = this.PaymentProviderEntities
             const changeRequest =
               this.subscription.getRequestForAddOnPurchase('some-add-on')
             expect(changeRequest).to.deep.equal(
-              new RecurlySubscriptionChangeRequest({
+              new PaymentProviderSubscriptionChangeRequest({
                 subscription: this.subscription,
                 timeframe: 'now',
                 addOnUpdates: [
-                  new RecurlySubscriptionAddOnUpdate({
+                  new PaymentProviderSubscriptionAddOnUpdate({
                     code: 'some-add-on',
                     quantity: 1,
                   }),
@@ -356,10 +364,10 @@ describe('RecurlyEntities', function () {
     })
   })
 
-  describe('RecurlySubscriptionChange', function () {
+  describe('PaymentProviderSubscriptionChange', function () {
     describe('constructor', function () {
       it('rounds the amounts when calculating the taxes', function () {
-        const subscription = new RecurlySubscription({
+        const subscription = new PaymentProviderSubscription({
           id: 'subscription-id',
           userId: 'user-id',
           planCode: 'premium-plan',
@@ -374,7 +382,7 @@ describe('RecurlyEntities', function () {
           periodEnd: new Date(),
           collectionMethod: 'automatic',
         })
-        const change = new RecurlySubscriptionChange({
+        const change = new PaymentProviderSubscriptionChange({
           subscription,
           nextPlanCode: 'promotional-plan',
           nextPlanName: 'Promotial plan',

+ 7 - 7
services/web/test/unit/src/Subscription/PaymentServiceTests.js

@@ -2,10 +2,10 @@ const sinon = require('sinon')
 const { expect } = require('chai')
 const SandboxedModule = require('sandboxed-module')
 const {
-  RecurlySubscription,
-  RecurlyAccount,
-  RecurlyCoupon,
-} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
+  PaymentProviderSubscription,
+  PaymentProviderAccount,
+  PaymentProviderCoupon,
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
 const MODULE_PATH = '../../../../app/src/Features/Subscription/PaymentService'
 
@@ -14,7 +14,7 @@ describe('PaymentService', function () {
     this.user = {
       _id: '123456',
     }
-    this.recurlySubscription = new RecurlySubscription({
+    this.recurlySubscription = new PaymentProviderSubscription({
       id: 'subscription-id',
       userId: this.user._id,
       currency: 'EUR',
@@ -30,13 +30,13 @@ describe('PaymentService', function () {
       periodEnd: new Date(),
       collectionMethod: 'automatic',
     })
-    this.recurlyAccount = new RecurlyAccount({
+    this.recurlyAccount = new PaymentProviderAccount({
       code: this.user._id,
       email: 'example@example.com',
       hasPastDueInvoice: true,
     })
     this.recurlyCoupons = [
-      new RecurlyCoupon({
+      new PaymentProviderCoupon({
         code: 'coupon-code',
         name: 'coupon name',
         description: 'coupon description',

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

@@ -3,12 +3,12 @@ const { expect } = require('chai')
 const recurly = require('recurly')
 const SandboxedModule = require('sandboxed-module')
 const {
-  RecurlySubscription,
-  RecurlySubscriptionChangeRequest,
-  RecurlySubscriptionAddOnUpdate,
-  RecurlyAccount,
-  RecurlyCoupon,
-} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
+  PaymentProviderSubscription,
+  PaymentProviderSubscriptionChangeRequest,
+  PaymentProviderSubscriptionAddOnUpdate,
+  PaymentProviderAccount,
+  PaymentProviderCoupon,
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
 const MODULE_PATH = '../../../../app/src/Features/Subscription/RecurlyClient'
 
@@ -41,7 +41,7 @@ describe('RecurlyClient', function () {
       preTaxTotal: 2,
     }
 
-    this.subscription = new RecurlySubscription({
+    this.subscription = new PaymentProviderSubscription({
       id: 'subscription-id',
       userId: 'user-id',
       currency: 'EUR',
@@ -156,7 +156,7 @@ describe('RecurlyClient', function () {
       const account = await this.RecurlyClient.promises.getAccountForUserId(
         this.user._id
       )
-      const expectedAccount = new RecurlyAccount({
+      const expectedAccount = new PaymentProviderAccount({
         code: this.user._id,
         email: this.user.email,
         hasPastDueInvoice: false,
@@ -226,7 +226,7 @@ describe('RecurlyClient', function () {
       const coupons =
         await this.RecurlyClient.promises.getActiveCouponsForUserId('some-user')
       const expectedCoupons = [
-        new RecurlyCoupon({
+        new PaymentProviderCoupon({
           code: 'coupon-code',
           name: 'Coupon Name',
           description: 'hosted page description',
@@ -329,7 +329,7 @@ describe('RecurlyClient', function () {
 
     it('handles plan changes', async function () {
       await this.RecurlyClient.promises.applySubscriptionChangeRequest(
-        new RecurlySubscriptionChangeRequest({
+        new PaymentProviderSubscriptionChangeRequest({
           subscription: this.subscription,
           timeframe: 'now',
           planCode: 'new-plan',
@@ -343,11 +343,11 @@ describe('RecurlyClient', function () {
 
     it('handles add-on changes', async function () {
       await this.RecurlyClient.promises.applySubscriptionChangeRequest(
-        new RecurlySubscriptionChangeRequest({
+        new PaymentProviderSubscriptionChangeRequest({
           subscription: this.subscription,
           timeframe: 'now',
           addOnUpdates: [
-            new RecurlySubscriptionAddOnUpdate({
+            new PaymentProviderSubscriptionAddOnUpdate({
               code: 'new-add-on',
               quantity: 2,
               unitPrice: 8.99,
@@ -514,7 +514,7 @@ describe('RecurlyClient', function () {
         })
         const { immediateCharge } =
           await this.RecurlyClient.promises.previewSubscriptionChange(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               planCode: 'new-plan',
@@ -546,7 +546,7 @@ describe('RecurlyClient', function () {
         })
         const { immediateCharge } =
           await this.RecurlyClient.promises.previewSubscriptionChange(
-            new RecurlySubscriptionChangeRequest({
+            new PaymentProviderSubscriptionChangeRequest({
               subscription: this.subscription,
               timeframe: 'now',
               planCode: 'new-plan',
@@ -570,7 +570,7 @@ describe('RecurlyClient', function () {
         .throws(new ValidationError())
       await expect(
         this.RecurlyClient.promises.previewSubscriptionChange(
-          new RecurlySubscriptionChangeRequest({
+          new PaymentProviderSubscriptionChangeRequest({
             subscription: this.subscription,
             timeframe: 'now',
             planCode: 'new-plan',
@@ -583,7 +583,7 @@ describe('RecurlyClient', function () {
       this.client.previewSubscriptionChange = sinon.stub().throws(new Error())
       await expect(
         this.RecurlyClient.promises.previewSubscriptionChange(
-          new RecurlySubscriptionChangeRequest({
+          new PaymentProviderSubscriptionChangeRequest({
             subscription: this.subscription,
             timeframe: 'now',
             planCode: 'new-plan',

+ 17 - 16
services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js

@@ -15,12 +15,12 @@ describe('SubscriptionGroupHandler', function () {
     this.subscription_id = '31DSd1123D'
     this.adding = 1
     this.paymentMethod = { cardType: 'Visa', lastFour: '1111' }
-    this.RecurlyEntities = {
+    this.PaymentProviderEntities = {
       MEMBERS_LIMIT_ADD_ON_CODE: 'additional-license',
     }
     this.localPlanInSettings = {
       membersLimit: 5,
-      membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+      membersLimitAddOn: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
     }
 
     this.subscription = {
@@ -40,7 +40,7 @@ describe('SubscriptionGroupHandler', function () {
       id: 123,
       addOns: [
         {
-          code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+          code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
           quantity: 1,
         },
       ],
@@ -98,7 +98,7 @@ describe('SubscriptionGroupHandler', function () {
     this.previewSubscriptionChange = {
       nextAddOns: [
         {
-          code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+          code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
           quantity: this.recurlySubscription.addOns[0].quantity + this.adding,
         },
       ],
@@ -166,7 +166,7 @@ describe('SubscriptionGroupHandler', function () {
         },
         './RecurlyClient': this.RecurlyClient,
         './PlansLocator': this.PlansLocator,
-        './RecurlyEntities': this.RecurlyEntities,
+        './PaymentProviderEntities': this.PaymentProviderEntities,
         '../Authentication/SessionManager': this.SessionManager,
         './GroupPlansData': this.GroupPlansData,
       },
@@ -325,7 +325,8 @@ describe('SubscriptionGroupHandler', function () {
         },
         plan: {
           membersLimit: 5,
-          membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+          membersLimitAddOn:
+            this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
           canUseFlexibleLicensing: true,
         },
         recurlySubscription: this.recurlySubscription,
@@ -347,14 +348,14 @@ describe('SubscriptionGroupHandler', function () {
       beforeEach(function () {
         this.recurlySubscription.addOns = [
           {
-            code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+            code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
             quantity: 6,
           },
         ]
         this.prevQuantity = this.recurlySubscription.addOns[0].quantity
         this.previewSubscriptionChange.nextAddOns = [
           {
-            code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+            code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
             quantity: this.prevQuantity + this.adding,
           },
         ]
@@ -367,7 +368,7 @@ describe('SubscriptionGroupHandler', function () {
 
         this.recurlySubscription.getRequestForAddOnUpdate
           .calledWith(
-            this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+            this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
             this.recurlySubscription.addOns[0].quantity + this.adding
           )
           .should.equal(true)
@@ -391,7 +392,7 @@ describe('SubscriptionGroupHandler', function () {
               {
                 type: 'add-on-update',
                 addOn: {
-                  code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+                  code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
                   quantity:
                     this.previewSubscriptionChange.nextAddOns[0].quantity,
                   prevQuantity: this.prevQuantity,
@@ -435,7 +436,7 @@ describe('SubscriptionGroupHandler', function () {
         this.prevQuantity = this.recurlySubscription.addOns[0]?.quantity ?? 0
         this.previewSubscriptionChange.nextAddOns = [
           {
-            code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+            code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
             quantity: this.prevQuantity + this.adding,
           },
         ]
@@ -467,7 +468,7 @@ describe('SubscriptionGroupHandler', function () {
               {
                 type: 'add-on-update',
                 addOn: {
-                  code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+                  code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
                   quantity:
                     this.previewSubscriptionChange.nextAddOns[0].quantity,
                   prevQuantity: this.prevQuantity,
@@ -493,7 +494,7 @@ describe('SubscriptionGroupHandler', function () {
             )
           this.recurlySubscription.getRequestForAddOnPurchase
             .calledWithExactly(
-              this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+              this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
               this.adding,
               this.GroupPlansData.enterprise.collaborator.USD[5]
                 .additional_license_legacy_price_in_cents / 100
@@ -513,7 +514,7 @@ describe('SubscriptionGroupHandler', function () {
             )
           this.recurlySubscription.getRequestForAddOnPurchase
             .calledWithExactly(
-              this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+              this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
               this.adding,
               undefined
             )
@@ -538,7 +539,7 @@ describe('SubscriptionGroupHandler', function () {
             )
           this.recurlySubscription.getRequestForAddOnPurchase
             .calledWithExactly(
-              this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+              this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
               this.adding,
               this.GroupPlansData.enterprise.collaborator.USD[5]
                 .additional_license_legacy_price_in_cents / 100
@@ -563,7 +564,7 @@ describe('SubscriptionGroupHandler', function () {
             )
           this.recurlySubscription.getRequestForAddOnPurchase
             .calledWithExactly(
-              this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
+              this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
               this.adding,
               undefined
             )

+ 6 - 6
services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js

@@ -3,9 +3,9 @@ const sinon = require('sinon')
 const chai = require('chai')
 const { expect } = chai
 const {
-  RecurlySubscription,
-  RecurlySubscriptionChangeRequest,
-} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
+  PaymentProviderSubscription,
+  PaymentProviderSubscriptionChangeRequest,
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
 const MODULE_PATH =
   '../../../../app/src/Features/Subscription/SubscriptionHandler'
@@ -27,7 +27,7 @@ const mockRecurlySubscriptions = {
 }
 
 const mockRecurlyClientSubscriptions = {
-  'subscription-123-active': new RecurlySubscription({
+  'subscription-123-active': new PaymentProviderSubscription({
     id: 'subscription-123-active',
     userId: 'user-id',
     planCode: 'collaborator',
@@ -275,7 +275,7 @@ describe('SubscriptionHandler', function () {
         expect(
           this.RecurlyClient.promises.applySubscriptionChangeRequest
         ).to.have.been.calledWith(
-          new RecurlySubscriptionChangeRequest({
+          new PaymentProviderSubscriptionChangeRequest({
             subscription: this.activeRecurlyClientSubscription,
             timeframe: 'now',
             planCode: this.plan_code,
@@ -388,7 +388,7 @@ describe('SubscriptionHandler', function () {
         expect(
           this.RecurlyClient.promises.applySubscriptionChangeRequest
         ).to.be.calledWith(
-          new RecurlySubscriptionChangeRequest({
+          new PaymentProviderSubscriptionChangeRequest({
             subscription: this.activeRecurlyClientSubscription,
             timeframe: 'now',
             planCode: this.plan_code,

+ 41 - 40
services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js

@@ -2,11 +2,11 @@ const SandboxedModule = require('sandboxed-module')
 const sinon = require('sinon')
 const { assert } = require('chai')
 const {
-  RecurlyAccount,
-  RecurlySubscription,
-  RecurlySubscriptionAddOn,
-  RecurlySubscriptionChange,
-} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
+  PaymentProviderAccount,
+  PaymentProviderSubscription,
+  PaymentProviderSubscriptionAddOn,
+  PaymentProviderSubscriptionChange,
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
 const modulePath =
   '../../../../app/src/Features/Subscription/SubscriptionViewModelBuilder'
@@ -33,7 +33,7 @@ describe('SubscriptionViewModelBuilder', function () {
         state: 'active',
       },
     }
-    this.recurlySubscription = new RecurlySubscription({
+    this.paymentRecord = new PaymentProviderSubscription({
       id: this.recurlySubscription_id,
       userId: this.user._id,
       currency: 'EUR',
@@ -41,7 +41,7 @@ describe('SubscriptionViewModelBuilder', function () {
       planName: 'plan-name',
       planPrice: 13,
       addOns: [
-        new RecurlySubscriptionAddOn({
+        new PaymentProviderSubscriptionAddOn({
           code: 'addon-code',
           name: 'addon name',
           quantity: 1,
@@ -265,7 +265,7 @@ describe('SubscriptionViewModelBuilder', function () {
           plan: this.plan,
           recurlySubscription_id: this.recurlySubscription_id,
         }
-        this.recurlySubscription = {
+        this.paymentRecord = {
           state: 'active',
         }
         this.SubscriptionLocator.promises.getUsersSubscription
@@ -279,7 +279,7 @@ describe('SubscriptionViewModelBuilder', function () {
           .withArgs(this.individualSubscription.recurlySubscription_id, {
             includeAccount: true,
           })
-          .resolves(this.recurlySubscription)
+          .resolves(this.paymentRecord)
 
         const usersBestSubscription =
           await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
@@ -293,7 +293,7 @@ describe('SubscriptionViewModelBuilder', function () {
         )
         sinon.assert.calledWith(
           this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly,
-          this.recurlySubscription,
+          this.paymentRecord,
           this.individualSubscriptionWithoutRecurly
         )
         assert.deepEqual(usersBestSubscription, {
@@ -507,14 +507,14 @@ describe('SubscriptionViewModelBuilder', function () {
 
   describe('buildUsersSubscriptionViewModel', function () {
     describe('with a recurly subscription', function () {
-      it('adds recurly data to the personal subscription', async function () {
+      it('adds payment data to the personal subscription', async function () {
         this.SubscriptionLocator.getUsersSubscription.yields(
           null,
           this.individualSubscription
         )
         this.PaymentService.getPaymentFromRecord.yields(null, {
-          subscription: this.recurlySubscription,
-          account: new RecurlyAccount({
+          subscription: this.paymentRecord,
+          account: new PaymentProviderAccount({
             email: 'example@example.com',
             hasPastDueInvoice: false,
           }),
@@ -524,7 +524,7 @@ describe('SubscriptionViewModelBuilder', function () {
           await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
             this.user
           )
-        assert.deepEqual(result.personalSubscription.recurly, {
+        assert.deepEqual(result.personalSubscription.payment, {
           taxRate: 0.1,
           billingDetailsLink: '/user/subscription/recurly/billing-details',
           accountManagementLink:
@@ -564,28 +564,29 @@ describe('SubscriptionViewModelBuilder', function () {
           null,
           this.individualSubscription
         )
-        this.recurlySubscription.pendingChange = new RecurlySubscriptionChange({
-          subscription: this.recurlySubscription,
-          nextPlanCode: this.groupPlanCode,
-          nextPlanName: 'Group Collaborator (Annual) 4 licenses',
-          nextPlanPrice: 1400,
-          nextAddOns: [
-            new RecurlySubscriptionAddOn({
-              code: 'additional-license',
-              name: 'additional license',
-              quantity: 8,
-              unitPrice: 24.4,
-            }),
-            new RecurlySubscriptionAddOn({
-              code: 'addon-code',
-              name: 'addon name',
-              quantity: 1,
-              unitPrice: 2,
-            }),
-          ],
-        })
+        this.paymentRecord.pendingChange =
+          new PaymentProviderSubscriptionChange({
+            subscription: this.paymentRecord,
+            nextPlanCode: this.groupPlanCode,
+            nextPlanName: 'Group Collaborator (Annual) 4 licenses',
+            nextPlanPrice: 1400,
+            nextAddOns: [
+              new PaymentProviderSubscriptionAddOn({
+                code: 'additional-license',
+                name: 'additional license',
+                quantity: 8,
+                unitPrice: 24.4,
+              }),
+              new PaymentProviderSubscriptionAddOn({
+                code: 'addon-code',
+                name: 'addon name',
+                quantity: 1,
+                unitPrice: 2,
+              }),
+            ],
+          })
         this.PaymentService.getPaymentFromRecord.yields(null, {
-          subscription: this.recurlySubscription,
+          subscription: this.paymentRecord,
           account: {},
           coupons: [],
         })
@@ -594,24 +595,24 @@ describe('SubscriptionViewModelBuilder', function () {
             this.user
           )
         assert.equal(
-          result.personalSubscription.recurly.displayPrice,
+          result.personalSubscription.payment.displayPrice,
           '€1,756.92'
         )
         assert.equal(
-          result.personalSubscription.recurly.planOnlyDisplayPrice,
+          result.personalSubscription.payment.planOnlyDisplayPrice,
           '€1,754.72'
         )
         assert.deepEqual(
-          result.personalSubscription.recurly
+          result.personalSubscription.payment
             .addOnDisplayPricesWithoutAdditionalLicense,
           { 'addon-code': '€2.20' }
         )
         assert.equal(
-          result.personalSubscription.recurly.pendingAdditionalLicenses,
+          result.personalSubscription.payment.pendingAdditionalLicenses,
           8
         )
         assert.equal(
-          result.personalSubscription.recurly.pendingTotalLicenses,
+          result.personalSubscription.payment.pendingTotalLicenses,
           12
         )
       })

+ 14 - 9
services/web/types/subscription/dashboard/subscription.ts

@@ -1,6 +1,11 @@
 import { CurrencyCode } from '../currency'
 import { Nullable } from '../../utils'
-import { Plan, AddOn, RecurlyAddOn, PendingRecurlyPlan } from '../plan'
+import {
+  Plan,
+  AddOn,
+  PaymentProviderAddOn,
+  PendingPaymentProviderPlan,
+} from '../plan'
 import { User } from '../../user'
 
 type SubscriptionState = 'active' | 'canceled' | 'expired' | 'paused'
@@ -10,18 +15,18 @@ export type PurchasingAddOnCode = {
   code: string
 }
 
-type RecurlyCoupon = {
+type PaymentProviderCoupon = {
   code: string
   name: string
   description: string
 }
 
-type Recurly = {
+type PaymentProviderRecord = {
   taxRate: number
   billingDetailsLink: string
   accountManagementLink: string
   additionalLicenses: number
-  addOns: RecurlyAddOn[]
+  addOns: PaymentProviderAddOn[]
   totalLicenses: number
   nextPaymentDueAt: string
   nextPaymentDueDate: string
@@ -29,7 +34,7 @@ type Recurly = {
   state?: SubscriptionState
   trialEndsAtFormatted: Nullable<string>
   trialEndsAt: Nullable<string>
-  activeCoupons: RecurlyCoupon[]
+  activeCoupons: PaymentProviderCoupon[]
   accountEmail: string
   hasPastDueInvoice: boolean
   displayPrice: string
@@ -58,19 +63,19 @@ export type Subscription = {
   planCode: string
   recurlySubscription_id: string
   plan: Plan
-  pendingPlan?: PendingRecurlyPlan
+  pendingPlan?: PendingPaymentProviderPlan
   addOns?: AddOn[]
 }
 
-export type RecurlySubscription = Subscription & {
-  recurly: Recurly
+export type PaidSubscription = Subscription & {
+  payment: PaymentProviderRecord
 }
 
 export type CustomSubscription = Subscription & {
   customAccount: boolean
 }
 
-export type GroupSubscription = RecurlySubscription & {
+export type GroupSubscription = PaidSubscription & {
   teamName: string
   teamNotice?: string
 }

+ 4 - 4
services/web/types/subscription/plan.ts

@@ -22,8 +22,8 @@ export type AddOn = {
   unitAmountInCents: number
 }
 
-// add-ons directly accessed through recurly
-export type RecurlyAddOn = {
+// add-ons directly accessed through payment
+export type PaymentProviderAddOn = {
   code: string
   name: string
   quantity: number
@@ -32,11 +32,11 @@ export type RecurlyAddOn = {
   displayPrice?: string
 }
 
-export type PendingRecurlyPlan = {
+export type PendingPaymentProviderPlan = {
   annual?: boolean
   displayPrice?: string
   featureDescription?: Record<string, unknown>[]
-  addOns?: RecurlyAddOn[]
+  addOns?: PaymentProviderAddOn[]
   features?: Features
   groupPlan?: boolean
   hideFromUsers?: boolean