diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index e88cb53449..7b8f989458 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -14,6 +14,7 @@ const ProjectHelper = require('./ProjectHelper') const metrics = require('@overleaf/metrics') const { User } = require('../../models/User') const SubscriptionLocator = require('../Subscription/SubscriptionLocator') +const { isPaidSubscription } = require('../Subscription/SubscriptionHelper') const LimitationsManager = require('../Subscription/LimitationsManager') const Settings = require('@overleaf/settings') const AuthorizationManager = require('../Authorization/AuthorizationManager') @@ -655,12 +656,11 @@ const _ProjectController = { } } - const hasNonRecurlySubscription = - subscription && !subscription.recurlySubscription_id + const hasPaidSubscription = isPaidSubscription(subscription) const hasManuallyCollectedSubscription = subscription?.collectionMethod === 'manual' const canPurchaseAddons = !( - hasNonRecurlySubscription || hasManuallyCollectedSubscription + hasPaidSubscription || hasManuallyCollectedSubscription ) const assistantDisabled = user.aiErrorAssistant?.enabled === false // the assistant has been manually disabled by the user const canUseErrorAssistant = @@ -792,7 +792,7 @@ const _ProjectController = { referal_id: user.referal_id, signUpDate: user.signUpDate, allowedFreeTrial, - hasRecurlySubscription: subscription?.recurlySubscription_id != null, + hasPaidSubscription, featureSwitches: user.featureSwitches, features: fullFeatureSet, featureUsage, diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs index c62396e153..1faa2df017 100644 --- a/services/web/app/src/Features/Project/ProjectListController.mjs +++ b/services/web/app/src/Features/Project/ProjectListController.mjs @@ -26,6 +26,7 @@ import GeoIpLookup from '../../infrastructure/GeoIpLookup.js' import SplitTestHandler from '../SplitTests/SplitTestHandler.js' import SplitTestSessionHandler from '../SplitTests/SplitTestSessionHandler.js' import TutorialHandler from '../Tutorial/TutorialHandler.js' +import SubscriptionHelper from '../Subscription/SubscriptionHelper.js' /** * @import { GetProjectsRequest, GetProjectsResponse, AllUsersProjects, MongoProject } from "./types" @@ -388,13 +389,13 @@ async function projectListPage(req, res, next) { } } - let hasIndividualRecurlySubscription = false + let hasIndividualPaidSubscription = false try { - hasIndividualRecurlySubscription = - usersIndividualSubscription?.groupPlan === false && - usersIndividualSubscription?.recurlyStatus?.state !== 'canceled' && - usersIndividualSubscription?.recurlySubscription_id !== '' + hasIndividualPaidSubscription = + SubscriptionHelper.isIndividualActivePaidSubscription( + usersIndividualSubscription + ) } catch (error) { logger.error({ err: error }, 'Failed to get individual subscription') } @@ -437,7 +438,7 @@ async function projectListPage(req, res, next) { groupId: subscription._id, groupName: subscription.teamName, })), - hasIndividualRecurlySubscription, + hasIndividualPaidSubscription, userRestrictions: Array.from(req.userRestrictions || []), }) } diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index a8c27f705f..16413c501c 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -3,6 +3,7 @@ const { callbackify } = require('util') const { callbackifyMultiResult } = require('@overleaf/promise-utils') const PlansLocator = require('./PlansLocator') const SubscriptionLocator = require('./SubscriptionLocator') +const SubscriptionHelper = require('./SubscriptionHelper') const UserFeaturesUpdater = require('./UserFeaturesUpdater') const FeaturesHelper = require('./FeaturesHelper') const Settings = require('@overleaf/settings') @@ -117,7 +118,10 @@ async function computeFeatures(userId) { async function _getIndividualFeatures(userId) { const subscription = await SubscriptionLocator.promises.getUsersSubscription(userId) - if (subscription == null || subscription?.recurlyStatus?.state === 'paused') { + if ( + subscription == null || + SubscriptionHelper.getPaidSubscriptionState(subscription) === 'paused' + ) { return {} } diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index aa0b97d497..d7de79f5a4 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -2,6 +2,7 @@ const SessionManager = require('../Authentication/SessionManager') const SubscriptionHandler = require('./SubscriptionHandler') +const SubscriptionHelper = require('./SubscriptionHelper') const SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder') const LimitationsManager = require('./LimitationsManager') const RecurlyWrapper = require('./RecurlyWrapper') @@ -262,7 +263,8 @@ async function pauseSubscription(req, res, next) { { pause_length: pauseCycles, plan_code: subscription?.planCode, - subscriptionId: subscription?.recurlySubscription_id, + subscriptionId: + SubscriptionHelper.getPaymentProviderSubscriptionId(subscription), } ) diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js index c717b2eec6..ba862baa67 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js @@ -4,6 +4,7 @@ const OError = require('@overleaf/o-error') const SubscriptionUpdater = require('./SubscriptionUpdater') const SubscriptionLocator = require('./SubscriptionLocator') const SubscriptionController = require('./SubscriptionController') +const SubscriptionHelper = require('./SubscriptionHelper') const { Subscription } = require('../../models/Subscription') const { User } = require('../../models/User') const RecurlyClient = require('./RecurlyClient') @@ -77,7 +78,7 @@ async function ensureFlexibleLicensingEnabled(plan) { } async function ensureSubscriptionIsActive(subscription) { - if (subscription?.recurlyStatus?.state !== 'active') { + if (SubscriptionHelper.getPaidSubscriptionState(subscription) !== 'active') { throw new InactiveError('The subscription is not active', { subscriptionId: subscription._id.toString(), }) diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index 8aa0ee84eb..9471974b08 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -4,6 +4,7 @@ const RecurlyWrapper = require('./RecurlyWrapper') const RecurlyClient = require('./RecurlyClient') const { User } = require('../../models/User') const logger = require('@overleaf/logger') +const SubscriptionHelper = require('./SubscriptionHelper') const SubscriptionUpdater = require('./SubscriptionUpdater') const SubscriptionLocator = require('./SubscriptionLocator') const LimitationsManager = require('./LimitationsManager') @@ -101,8 +102,7 @@ async function updateSubscription(user, planCode) { if ( !hasSubscription || subscription == null || - (subscription.recurlySubscription_id == null && - subscription.paymentProvider?.subscriptionId == null) + SubscriptionHelper.getPaymentProviderSubscriptionId(subscription) == null ) { return } @@ -299,7 +299,10 @@ async function pauseSubscription(user, pauseCycles) { // only allow pausing on monthly plans not in a trial const { subscription } = await LimitationsManager.promises.userHasSubscription(user) - if (!subscription || !subscription.recurlyStatus) { + if ( + !subscription || + !SubscriptionHelper.getPaidSubscriptionState(subscription) + ) { throw new Error('No active subscription to pause') } @@ -310,10 +313,9 @@ async function pauseSubscription(user, pauseCycles) { ) { throw new Error('Can only pause monthly individual plans') } - if ( - subscription.recurlyStatus.trialEndsAt && - subscription.recurlyStatus.trialEndsAt > new Date() - ) { + const trialEndsAt = + SubscriptionHelper.getSubscriptionTrialEndsAt(subscription) + if (trialEndsAt && trialEndsAt > new Date()) { throw new Error('Cannot pause a subscription in a trial') } if (subscription.addOns?.length) { @@ -329,7 +331,10 @@ async function pauseSubscription(user, pauseCycles) { async function resumeSubscription(user) { const { subscription } = await LimitationsManager.promises.userHasSubscription(user) - if (!subscription || !subscription.recurlyStatus) { + if ( + !subscription || + !SubscriptionHelper.getPaidSubscriptionState(subscription) + ) { throw new Error('No active subscription to resume') } await RecurlyClient.promises.resumeSubscriptionByUuid( diff --git a/services/web/app/src/Features/Subscription/SubscriptionHelper.js b/services/web/app/src/Features/Subscription/SubscriptionHelper.js index efb8895280..b4acef4d3e 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHelper.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHelper.js @@ -86,7 +86,66 @@ function generateInitialLocalizedGroupPrice(recommendedCurrency, locale) { } } +function isPaidSubscription(subscription) { + const hasRecurlySubscription = + subscription?.recurlySubscription_id && + subscription?.recurlySubscription_id !== '' + const hasStripeSubscription = + subscription?.paymentProvider?.subscriptionId && + subscription?.paymentProvider?.subscriptionId !== '' + return !!(subscription && (hasRecurlySubscription || hasStripeSubscription)) +} + +function isIndividualActivePaidSubscription(subscription) { + return ( + isPaidSubscription(subscription) && + subscription?.groupPlan === false && + subscription?.recurlyStatus?.state !== 'canceled' && + subscription?.paymentProvider?.state !== 'canceled' + ) +} + +function getPaymentProviderSubscriptionId(subscription) { + if (subscription?.recurlySubscription_id) { + return subscription.recurlySubscription_id + } + if (subscription?.paymentProvider?.subscriptionId) { + return subscription.paymentProvider.subscriptionId + } + return null +} + +function getPaidSubscriptionState(subscription) { + if (subscription?.recurlyStatus?.state) { + return subscription.recurlyStatus.state + } + if (subscription?.paymentProvider?.state) { + return subscription.paymentProvider.state + } + return null +} + +function getSubscriptionTrialStartedAt(subscription) { + if (subscription?.recurlyStatus) { + return subscription.recurlyStatus?.trialStartedAt + } + return subscription?.paymentProvider?.trialStartedAt +} + +function getSubscriptionTrialEndsAt(subscription) { + if (subscription?.recurlyStatus) { + return subscription.recurlyStatus?.trialEndsAt + } + return subscription?.paymentProvider?.trialEndsAt +} + module.exports = { shouldPlanChangeAtTermEnd, generateInitialLocalizedGroupPrice, + isPaidSubscription, + isIndividualActivePaidSubscription, + getPaymentProviderSubscriptionId, + getPaidSubscriptionState, + getSubscriptionTrialStartedAt, + getSubscriptionTrialEndsAt, } diff --git a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js index 441d9c2c9b..25b00c28a5 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js +++ b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js @@ -1,6 +1,5 @@ // ts-check const Settings = require('@overleaf/settings') -const RecurlyWrapper = require('./RecurlyWrapper') const PlansLocator = require('./PlansLocator') const { isStandaloneAiAddOnPlanCode, @@ -8,7 +7,6 @@ const { } = require('./PaymentProviderEntities') const SubscriptionFormatters = require('./SubscriptionFormatters') const SubscriptionLocator = require('./SubscriptionLocator') -const SubscriptionUpdater = require('./SubscriptionUpdater') const InstitutionsGetter = require('../Institutions/InstitutionsGetter') const InstitutionsManager = require('../Institutions/InstitutionsManager') const PublishersGetter = require('../Publishers/PublishersGetter') @@ -227,6 +225,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { // don't return subscription payment information delete personalSubscription.paymentProvider delete personalSubscription.recurly + delete personalSubscription.recurlySubscription_id const tax = paymentRecord.subscription.taxAmount || 0 // Some plans allow adding more seats than the base plan provides. @@ -374,15 +373,6 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { } } -/** - * @param {{_id: string}} user - * @returns {Promise} - */ -async function getBestSubscription(user) { - const { bestSubscription } = await getUsersSubscriptionDetails(user) - return bestSubscription -} - /** * @param {{_id: string}} user * @returns {Promise<{bestSubscription:Subscription,individualSubscription:DBSubscription|null,memberGroupSubscriptions:DBSubscription[]}>} @@ -400,15 +390,18 @@ async function getUsersSubscriptionDetails(user) { if ( individualSubscription && !individualSubscription.customAccount && - individualSubscription.recurlySubscription_id && - !individualSubscription.recurlyStatus?.state + SubscriptionHelper.getPaymentProviderSubscriptionId( + individualSubscription + ) && + !SubscriptionHelper.getPaidSubscriptionState(individualSubscription) ) { - const recurlySubscription = await RecurlyWrapper.promises.getSubscription( - individualSubscription.recurlySubscription_id, - { includeAccount: true } + const paymentResults = await Modules.promises.hooks.fire( + 'getPaymentFromRecordPromise', + individualSubscription ) - await SubscriptionUpdater.promises.updateSubscriptionFromRecurly( - recurlySubscription, + await Modules.promises.hooks.fire( + 'syncSubscription', + paymentResults[0]?.subscription, individualSubscription ) individualSubscription = @@ -540,7 +533,8 @@ function _isPlanEqualOrBetter(planA, planB) { function _getRemainingTrialDays(subscription) { const now = new Date() - const trialEndDate = subscription.recurlyStatus?.trialEndsAt + const trialEndDate = + SubscriptionHelper.getSubscriptionTrialEndsAt(subscription) return trialEndDate && trialEndDate > now ? Math.ceil( (trialEndDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) @@ -605,10 +599,8 @@ module.exports = { buildUsersSubscriptionViewModel: callbackify(buildUsersSubscriptionViewModel), buildPlansList, buildPlansListForSubscriptionDash, - getBestSubscription: callbackify(getBestSubscription), promises: { buildUsersSubscriptionViewModel, - getBestSubscription, getUsersSubscriptionDetails, }, } diff --git a/services/web/app/src/Features/Subscription/TeamInvitesController.mjs b/services/web/app/src/Features/Subscription/TeamInvitesController.mjs index b2c9840de4..cbe46d2c29 100644 --- a/services/web/app/src/Features/Subscription/TeamInvitesController.mjs +++ b/services/web/app/src/Features/Subscription/TeamInvitesController.mjs @@ -4,6 +4,7 @@ import OError from '@overleaf/o-error' import TeamInvitesHandler from './TeamInvitesHandler.js' import SessionManager from '../Authentication/SessionManager.js' import SubscriptionLocator from './SubscriptionLocator.js' +import SubscriptionHelper from './SubscriptionHelper.js' import ErrorController from '../Errors/ErrorController.js' import EmailHelper from '../Helpers/EmailHelper.js' import UserGetter from '../User/UserGetter.js' @@ -87,12 +88,10 @@ async function viewInvite(req, res, next) { const personalSubscription = await SubscriptionLocator.promises.getUsersSubscription(userId) - const hasIndividualRecurlySubscription = - personalSubscription && - personalSubscription.groupPlan === false && - personalSubscription.recurlyStatus?.state !== 'canceled' && - personalSubscription.recurlySubscription_id && - personalSubscription.recurlySubscription_id !== '' + const hasIndividualPaidSubscription = + SubscriptionHelper.isIndividualActivePaidSubscription( + personalSubscription + ) if (subscription?.managedUsersEnabled) { if (!subscription.populated('groupPolicy')) { @@ -155,7 +154,7 @@ async function viewInvite(req, res, next) { return res.render('subscriptions/team/invite', { inviterName: invite.inviterName, inviteToken: invite.token, - hasIndividualRecurlySubscription, + hasIndividualPaidSubscription, expired: req.query.expired, userRestrictions: Array.from(req.userRestrictions || []), currentManagedUserAdminEmail, diff --git a/services/web/app/views/project/list-react.pug b/services/web/app/views/project/list-react.pug index be9233ecbb..60e7d0c0fc 100644 --- a/services/web/app/views/project/list-react.pug +++ b/services/web/app/views/project/list-react.pug @@ -34,7 +34,7 @@ block append meta meta(name="ol-recommendedCurrency" data-type="string" content=recommendedCurrency) meta(name="ol-showLATAMBanner" data-type="boolean" content=showLATAMBanner) meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment) - meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription) + meta(name="ol-hasIndividualPaidSubscription" data-type="boolean" content=hasIndividualPaidSubscription) meta(name="ol-groupSsoSetupSuccess" data-type="boolean" content=groupSsoSetupSuccess) meta(name="ol-showUSGovBanner" data-type="boolean" content=showUSGovBanner) meta(name="ol-usGovBannerVariant" data-type="string" content=usGovBannerVariant) diff --git a/services/web/app/views/subscriptions/team/invite.pug b/services/web/app/views/subscriptions/team/invite.pug index dc1b509cbf..1b2ecb4646 100644 --- a/services/web/app/views/subscriptions/team/invite.pug +++ b/services/web/app/views/subscriptions/team/invite.pug @@ -4,7 +4,7 @@ block entrypointVar - entrypoint = 'pages/user/subscription/invite' block append meta - meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription) + meta(name="ol-hasIndividualPaidSubscription" data-type="boolean" content=hasIndividualPaidSubscription) meta(name="ol-inviterName" data-type="string" content=inviterName) meta(name="ol-inviteToken" data-type="string" content=inviteToken) meta(name="ol-currentManagedUserAdminEmail" data-type="string" content=currentManagedUserAdminEmail) diff --git a/services/web/frontend/js/features/project-list/components/current-plan-widget/current-plan-widget.tsx b/services/web/frontend/js/features/project-list/components/current-plan-widget/current-plan-widget.tsx index 20bfe55479..1d17fe75a3 100644 --- a/services/web/frontend/js/features/project-list/components/current-plan-widget/current-plan-widget.tsx +++ b/services/web/frontend/js/features/project-list/components/current-plan-widget/current-plan-widget.tsx @@ -4,6 +4,7 @@ import GroupPlan from './group-plan' import CommonsPlan from './commons-plan' import PausedPlan from './paused-plan' import getMeta from '../../../../utils/meta' +import { getUserSubscriptionState } from '../../util/user' function CurrentPlanWidget() { const usersBestSubscription = getMeta('ol-usersBestSubscription') @@ -19,7 +20,7 @@ function CurrentPlanWidget() { const isCommonsPlan = type === 'commons' const isPaused = isIndividualPlan && - usersBestSubscription.subscription?.recurlyStatus?.state === 'paused' + getUserSubscriptionState(usersBestSubscription) === 'paused' const featuresPageURL = '/learn/how-to/Overleaf_premium_features' const subscriptionPageUrl = '/user/subscription' diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx index 6c25513124..15248f8c42 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx @@ -57,19 +57,19 @@ export function useGroupInvitationNotification( const location = useLocation() const { handleDismiss } = useAsyncDismiss() - const hasIndividualRecurlySubscription = getMeta( - 'ol-hasIndividualRecurlySubscription' + const hasIndividualPaidSubscription = getMeta( + 'ol-hasIndividualPaidSubscription' ) useEffect(() => { - if (hasIndividualRecurlySubscription) { + if (hasIndividualPaidSubscription) { setGroupInvitationStatus( GroupInvitationStatus.CancelIndividualSubscription ) } else { setGroupInvitationStatus(GroupInvitationStatus.AskToJoin) } - }, [hasIndividualRecurlySubscription]) + }, [hasIndividualPaidSubscription]) const acceptGroupInvite = useCallback(() => { if (managedUsersEnabled) { diff --git a/services/web/frontend/js/features/project-list/util/user.ts b/services/web/frontend/js/features/project-list/util/user.ts index cb63ba3aee..115ad03cbc 100644 --- a/services/web/frontend/js/features/project-list/util/user.ts +++ b/services/web/frontend/js/features/project-list/util/user.ts @@ -1,4 +1,5 @@ import { UserRef } from '../../../../../types/project/dashboard/api' +import { Subscription } from '../../../../../types/project/dashboard/subscription' import getMeta from '@/utils/meta' export function getUserName(user: UserRef) { @@ -20,3 +21,16 @@ export function getUserName(user: UserRef) { return 'None' } + +export function getUserSubscriptionState(subscription: Subscription) { + if ('subscription' in subscription) { + if (subscription.subscription.recurlyStatus) { + return subscription.subscription.recurlyStatus.state + } + if (subscription.subscription.paymentProvider) { + return subscription.subscription.paymentProvider.state + } + } + + return null +} diff --git a/services/web/frontend/js/features/subscription/components/group-invite/group-invite.tsx b/services/web/frontend/js/features/subscription/components/group-invite/group-invite.tsx index a4e8fb2da8..66b6288388 100644 --- a/services/web/frontend/js/features/subscription/components/group-invite/group-invite.tsx +++ b/services/web/frontend/js/features/subscription/components/group-invite/group-invite.tsx @@ -19,20 +19,20 @@ export type InviteViewTypes = | undefined function GroupInviteViews() { - const hasIndividualRecurlySubscription = getMeta( - 'ol-hasIndividualRecurlySubscription' + const hasIndividualPaidSubscription = getMeta( + 'ol-hasIndividualPaidSubscription' ) const cannotJoinSubscription = getMeta('ol-cannot-join-subscription') useEffect(() => { if (cannotJoinSubscription) { setView('managed-user-cannot-join') - } else if (hasIndividualRecurlySubscription) { + } else if (hasIndividualPaidSubscription) { setView('cancel-personal-subscription') } else { setView('invite') } - }, [cannotJoinSubscription, hasIndividualRecurlySubscription]) + }, [cannotJoinSubscription, hasIndividualPaidSubscription]) const [view, setView] = useState(undefined) if (!view) { diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index f2692a0b7b..f574b1154e 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -127,7 +127,7 @@ export interface Meta { 'ol-groupsAndEnterpriseBannerVariant': GroupsAndEnterpriseBannerVariant 'ol-hasAiAssistViaWritefull': boolean 'ol-hasGroupSSOFeature': boolean - 'ol-hasIndividualRecurlySubscription': boolean + 'ol-hasIndividualPaidSubscription': boolean 'ol-hasManagedUsersFeature': boolean 'ol-hasPassword': boolean 'ol-hasSubscription': boolean diff --git a/services/web/frontend/stories/project-list/notifications.stories.tsx b/services/web/frontend/stories/project-list/notifications.stories.tsx index 90fa82bfa5..ea00f84681 100644 --- a/services/web/frontend/stories/project-list/notifications.stories.tsx +++ b/services/web/frontend/stories/project-list/notifications.stories.tsx @@ -186,7 +186,7 @@ export const NotificationGroupInvitationCancelSubscription = (args: any) => { }, }) - window.metaAttributesCache.set('ol-hasIndividualRecurlySubscription', true) + window.metaAttributesCache.set('ol-hasIndividualPaidSubscription', true) return ( diff --git a/services/web/test/frontend/components/project-list/notifications/group-invitation.spec.tsx b/services/web/test/frontend/components/project-list/notifications/group-invitation.spec.tsx index 5767302fed..31114a2405 100644 --- a/services/web/test/frontend/components/project-list/notifications/group-invitation.spec.tsx +++ b/services/web/test/frontend/components/project-list/notifications/group-invitation.spec.tsx @@ -62,10 +62,7 @@ describe('', function () { describe('user with existing personal subscription', function () { beforeEach(function () { - window.metaAttributesCache.set( - 'ol-hasIndividualRecurlySubscription', - true - ) + window.metaAttributesCache.set('ol-hasIndividualPaidSubscription', true) }) it('is able to join group successfully without cancelling personal subscription', function () { diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index 78c732ebe3..9a845283d7 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -441,7 +441,7 @@ describe('', function () { ), ]) window.metaAttributesCache.set( - 'ol-hasIndividualRecurlySubscription', + 'ol-hasIndividualPaidSubscription', true ) diff --git a/services/web/test/frontend/features/subscription/components/group-invite/group-invite.test.tsx b/services/web/test/frontend/features/subscription/components/group-invite/group-invite.test.tsx index cc70eff90d..d7b769fd20 100644 --- a/services/web/test/frontend/features/subscription/components/group-invite/group-invite.test.tsx +++ b/services/web/test/frontend/features/subscription/components/group-invite/group-invite.test.tsx @@ -18,10 +18,7 @@ describe('group invite', function () { describe('when user has personal subscription', function () { beforeEach(function () { - window.metaAttributesCache.set( - 'ol-hasIndividualRecurlySubscription', - true - ) + window.metaAttributesCache.set('ol-hasIndividualPaidSubscription', true) }) it('renders cancel personal subscription view', async function () { @@ -55,10 +52,7 @@ describe('group invite', function () { describe('when user does not have a personal subscription', function () { beforeEach(function () { - window.metaAttributesCache.set( - 'ol-hasIndividualRecurlySubscription', - false - ) + window.metaAttributesCache.set('ol-hasIndividualPaidSubscription', false) window.metaAttributesCache.set('ol-inviteToken', 'token123') }) diff --git a/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts b/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts index 08690742d3..8011c5206d 100644 --- a/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts +++ b/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts @@ -25,7 +25,6 @@ export const annualActiveSubscription: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator-annual', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator-annual', name: 'Standard (Collaborator) Annual', @@ -68,7 +67,6 @@ export const annualActiveSubscriptionEuro: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator-annual', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator-annual', name: 'Standard (Collaborator) Annual', @@ -111,7 +109,6 @@ export const annualActiveSubscriptionPro: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'professional', - recurlySubscription_id: 'ghi789', plan: { planCode: 'professional', name: 'Professional', @@ -153,7 +150,6 @@ export const pastDueExpiredSubscription: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator-annual', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator-annual', name: 'Standard (Collaborator) Annual', @@ -196,7 +192,6 @@ export const canceledSubscription: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator-annual', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator-annual', name: 'Standard (Collaborator) Annual', @@ -239,7 +234,6 @@ export const pendingSubscriptionChange: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator-annual', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator-annual', name: 'Standard (Collaborator) Annual', @@ -290,7 +284,6 @@ export const groupActiveSubscription: GroupSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'group_collaborator_10_enterprise', - recurlySubscription_id: 'ghi789', plan: { planCode: 'group_collaborator_10_enterprise', name: 'Overleaf Standard (Collaborator) - Group Account (10 licenses) - Enterprise', @@ -338,7 +331,6 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription admin_id: 'abc123', teamInvites: [], planCode: 'group_collaborator_10_enterprise', - recurlySubscription_id: 'ghi789', plan: { planCode: 'group_collaborator_10_enterprise', name: 'Overleaf Standard (Collaborator) - Group Account (10 licenses) - Enterprise', @@ -396,7 +388,6 @@ export const trialSubscription: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'paid-personal_free_trial_7_days', - recurlySubscription_id: 'ghi789', plan: { planCode: 'paid-personal_free_trial_7_days', name: 'Personal', @@ -439,7 +430,6 @@ export const customSubscription: CustomSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator-annual', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator-annual', name: 'Standard (Collaborator) Annual', @@ -460,7 +450,6 @@ export const trialCollaboratorSubscription: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator_free_trial_7_days', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator_free_trial_7_days', name: 'Standard (Collaborator)', @@ -503,7 +492,6 @@ export const monthlyActiveCollaborator: PaidSubscription = { admin_id: 'abc123', teamInvites: [], planCode: 'collaborator', - recurlySubscription_id: 'ghi789', plan: { planCode: 'collaborator', name: 'Standard (Collaborator)', diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js index 46427171da..7745ece8fa 100644 --- a/services/web/test/unit/src/Project/ProjectControllerTests.js +++ b/services/web/test/unit/src/Project/ProjectControllerTests.js @@ -201,9 +201,6 @@ describe('ProjectController', function () { getCurrentAffiliations: sinon.stub().resolves([]), }, } - this.SubscriptionViewModelBuilder = { - getBestSubscription: sinon.stub().yields(null, { type: 'free' }), - } this.SurveyHandler = { getSurvey: sinon.stub().yields(null, {}), } diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 879a31b917..087df52815 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -6,6 +6,7 @@ const MockResponse = require('../helpers/MockResponse') const modulePath = '../../../../app/src/Features/Subscription/SubscriptionController' const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors') +const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper') const mockSubscriptions = { 'subscription-123-active': { @@ -77,7 +78,6 @@ describe('SubscriptionController', function () { buildPlansList: sinon.stub(), promises: { buildUsersSubscriptionViewModel: sinon.stub().resolves({}), - getBestSubscription: sinon.stub().resolves({}), }, buildPlansListForSubscriptionDash: sinon .stub() @@ -146,7 +146,7 @@ describe('SubscriptionController', function () { '../SplitTests/SplitTestHandler': this.SplitTestV2Hander, '../Authentication/SessionManager': this.SessionManager, './SubscriptionHandler': this.SubscriptionHandler, - './SubscriptionHelper': this.SubscriptionHelper, + './SubscriptionHelper': SubscriptionHelper, './SubscriptionViewModelBuilder': this.SubscriptionViewModelBuilder, './LimitationsManager': this.LimitationsManager, '../../infrastructure/GeoIpLookup': this.GeoIpLookup, diff --git a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js index ed5ed2f6d1..7bf23defd2 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js @@ -5,6 +5,7 @@ const { expect } = chai const { PaymentProviderSubscription, } = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') +const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper') const MODULE_PATH = '../../../../app/src/Features/Subscription/SubscriptionHandler' @@ -149,6 +150,7 @@ describe('SubscriptionHandler', function () { '../../models/User': { User: this.User, }, + './SubscriptionHelper': SubscriptionHelper, './SubscriptionUpdater': this.SubscriptionUpdater, './SubscriptionLocator': this.SubscriptionLocator, './LimitationsManager': this.LimitationsManager, diff --git a/services/web/test/unit/src/Subscription/SubscriptionHelperTests.js b/services/web/test/unit/src/Subscription/SubscriptionHelperTests.js index a6e1ffa089..c700e67316 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionHelperTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionHelperTests.js @@ -267,4 +267,206 @@ describe('SubscriptionHelper', function () { }) }) }) + + describe('isPaidSubscription', function () { + it('should return true for a subscription with a recurly subscription id', function () { + const result = this.SubscriptionHelper.isPaidSubscription({ + recurlySubscription_id: 'some-id', + }) + expect(result).to.be.true + }) + + it('should return true for a subscription with a stripe subscription id', function () { + const result = this.SubscriptionHelper.isPaidSubscription({ + paymentProvider: { subscriptionId: 'some-id' }, + }) + expect(result).to.be.true + }) + + it('should return false for a free subscription', function () { + const result = this.SubscriptionHelper.isPaidSubscription({}) + expect(result).to.be.false + }) + + it('should return false for a missing subscription', function () { + const result = this.SubscriptionHelper.isPaidSubscription() + expect(result).to.be.false + }) + }) + + describe('isIndividualActivePaidSubscription', function () { + it('should return true for an active recurly subscription', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: false, + recurlyStatus: { state: 'active' }, + recurlySubscription_id: 'some-id', + } + ) + expect(result).to.be.true + }) + + it('should return true for an active stripe subscription', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: false, + paymentProvider: { subscriptionId: 'sub_123', state: 'active' }, + } + ) + expect(result).to.be.true + }) + + it('should return false for a canceled recurly subscription', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: false, + recurlyStatus: { state: 'canceled' }, + recurlySubscription_id: 'some-id', + } + ) + expect(result).to.be.false + }) + + it('should return false for a canceled stripe subscription', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: false, + paymentProvider: { state: 'canceled', subscriptionId: 'sub_123' }, + } + ) + expect(result).to.be.false + }) + + it('should return false for a group plan subscription', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: true, + recurlyStatus: { state: 'active' }, + recurlySubscription_id: 'some-id', + } + ) + expect(result).to.be.false + }) + + it('should return false for a free subscription', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + {} + ) + expect(result).to.be.false + }) + + it('should return false for a subscription with an empty string for recurlySubscription_id', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: false, + recurlySubscription_id: '', + recurlyStatus: { state: 'active' }, + } + ) + expect(result).to.be.false + }) + + it('should return false for a subscription with an empty string for paymentProvider.subscriptionId', function () { + const result = this.SubscriptionHelper.isIndividualActivePaidSubscription( + { + groupPlan: false, + paymentProvider: { state: 'active', subscriptionId: '' }, + } + ) + expect(result).to.be.false + }) + + it('should return false for a missing subscription', function () { + const result = this.SubscriptionHelper.isPaidSubscription() + expect(result).to.be.false + }) + }) + + describe('getPaymentProviderSubscriptionId', function () { + it('should return the recurly subscription id if it exists', function () { + const result = this.SubscriptionHelper.getPaymentProviderSubscriptionId({ + recurlySubscription_id: 'some-id', + }) + expect(result).to.equal('some-id') + }) + + it('should return the payment provider subscription id if it exists', function () { + const result = this.SubscriptionHelper.getPaymentProviderSubscriptionId({ + paymentProvider: { subscriptionId: 'sub_123' }, + }) + expect(result).to.equal('sub_123') + }) + + it('should return null if no subscription id exists', function () { + const result = this.SubscriptionHelper.getPaymentProviderSubscriptionId( + {} + ) + expect(result).to.be.null + }) + }) + + describe('getPaidSubscriptionState', function () { + it('should return the recurly state if it exists', function () { + const result = this.SubscriptionHelper.getPaidSubscriptionState({ + recurlyStatus: { state: 'active' }, + }) + expect(result).to.equal('active') + }) + + it('should return the payment provider state if it exists', function () { + const result = this.SubscriptionHelper.getPaidSubscriptionState({ + paymentProvider: { state: 'active' }, + }) + expect(result).to.equal('active') + }) + + it('should return null if no state exists', function () { + const result = this.SubscriptionHelper.getPaidSubscriptionState({}) + expect(result).to.be.null + }) + }) + + describe('getSubscriptionTrialStartedAt', function () { + it('should return the recurly trial start date if it exists', function () { + const result = this.SubscriptionHelper.getSubscriptionTrialStartedAt({ + recurlySubscription_id: 'some-id', + recurlyStatus: { trialStartedAt: new Date('2023-01-01') }, + }) + expect(result).to.deep.equal(new Date('2023-01-01')) + }) + + it('should return the payment provider trial start date if it exists', function () { + const result = this.SubscriptionHelper.getSubscriptionTrialStartedAt({ + paymentProvider: { trialStartedAt: new Date('2023-01-01') }, + }) + expect(result).to.deep.equal(new Date('2023-01-01')) + }) + + it('should return undefined if no trial start date exists', function () { + const result = this.SubscriptionHelper.getSubscriptionTrialStartedAt({}) + expect(result).to.be.undefined + }) + }) + + describe('getSubscriptionTrialEndsAt', function () { + it('should return the recurly trial end date if it exists', function () { + const result = this.SubscriptionHelper.getSubscriptionTrialEndsAt({ + recurlySubscription_id: 'some-id', + recurlyStatus: { trialEndsAt: new Date('2023-01-01') }, + }) + expect(result).to.deep.equal(new Date('2023-01-01')) + }) + + it('should return the payment provider trial end date if it exists', function () { + const result = this.SubscriptionHelper.getSubscriptionTrialEndsAt({ + paymentProvider: { trialEndsAt: new Date('2023-01-01') }, + }) + expect(result).to.deep.equal(new Date('2023-01-01')) + }) + + it('should return undefined if no trial end date exists', function () { + const result = this.SubscriptionHelper.getSubscriptionTrialEndsAt({}) + expect(result).to.be.undefined + }) + }) }) diff --git a/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js b/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js index a7c02f1e65..86eb51070e 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js @@ -7,6 +7,7 @@ const { PaymentProviderSubscriptionAddOn, PaymentProviderSubscriptionChange, } = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') +const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper') const modulePath = '../../../../app/src/Features/Subscription/SubscriptionViewModelBuilder' @@ -159,13 +160,14 @@ describe('SubscriptionViewModelBuilder', function () { './SubscriptionUpdater': this.SubscriptionUpdater, './PlansLocator': this.PlansLocator, '../../infrastructure/Modules': (this.Modules = { + promises: { hooks: { fire: sinon.stub().resolves([]) } }, hooks: { fire: sinon.stub().yields(null, []), }, }), './V1SubscriptionManager': {}, '../Publishers/PublishersGetter': this.PublishersGetter, - './SubscriptionHelper': {}, + './SubscriptionHelper': SubscriptionHelper, }, }) @@ -180,10 +182,10 @@ describe('SubscriptionViewModelBuilder', function () { .returns(this.commonsPlan) }) - describe('getBestSubscription', function () { + describe('getUsersSubscriptionDetails', function () { it('should return a free plan when user has no subscription or affiliation', async function () { - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) assert.deepEqual(usersBestSubscription, { type: 'free' }) @@ -195,8 +197,8 @@ describe('SubscriptionViewModelBuilder', function () { .withArgs(this.user) .resolves(this.individualCustomSubscription) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -213,8 +215,8 @@ describe('SubscriptionViewModelBuilder', function () { .withArgs(this.user) .resolves(this.individualSubscription) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -234,8 +236,8 @@ describe('SubscriptionViewModelBuilder', function () { .withArgs(this.user) .resolves(this.individualSubscription) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -255,8 +257,8 @@ describe('SubscriptionViewModelBuilder', function () { .withArgs(this.user) .resolves(this.individualSubscription) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -268,8 +270,8 @@ describe('SubscriptionViewModelBuilder', function () { }) }) - it('should update subscription if recurly data is missing', async function () { - this.individualSubscriptionWithoutRecurly = { + it('should update subscription if recurly payment state is missing', async function () { + this.individualSubscriptionWithoutPaymentState = { planCode: this.planCode, plan: this.plan, recurlySubscription_id: this.recurlySubscription_id, @@ -280,37 +282,104 @@ describe('SubscriptionViewModelBuilder', function () { this.SubscriptionLocator.promises.getUsersSubscription .withArgs(this.user) .onCall(0) - .resolves(this.individualSubscriptionWithoutRecurly) + .resolves(this.individualSubscriptionWithoutPaymentState) .withArgs(this.user) .onCall(1) .resolves(this.individualSubscription) - this.RecurlyWrapper.promises.getSubscription - .withArgs(this.individualSubscription.recurlySubscription_id, { - includeAccount: true, - }) - .resolves(this.paymentRecord) + const payment = { + subscription: this.paymentRecord, + account: new PaymentProviderAccount({}), + coupons: [], + } - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + this.Modules.promises.hooks.fire + .withArgs( + 'getPaymentFromRecordPromise', + this.individualSubscriptionWithoutPaymentState + ) + .resolves([payment]) + this.Modules.promises.hooks.fire + .withArgs( + 'syncSubscription', + payment, + this.individualSubscriptionWithoutPaymentState + ) + .resolves([]) + + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) - sinon.assert.calledWith( - this.RecurlyWrapper.promises.getSubscription, - this.individualSubscriptionWithoutRecurly.recurlySubscription_id, - { includeAccount: true } - ) - sinon.assert.calledWith( - this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly, - this.paymentRecord, - this.individualSubscriptionWithoutRecurly - ) assert.deepEqual(usersBestSubscription, { type: 'individual', subscription: this.individualSubscription, plan: this.plan, remainingTrialDays: -1, }) + assert.isTrue( + this.Modules.promises.hooks.fire.withArgs( + 'getPaymentFromRecordPromise', + this.individualSubscriptionWithoutPaymentState + ).calledOnce + ) + }) + + it('should update subscription if stripe payment state is missing', async function () { + this.individualSubscriptionWithoutPaymentState = { + planCode: this.planCode, + plan: this.plan, + paymentProvider: { + subscriptionId: this.recurlySubscription_id, + }, + } + this.paymentRecord = { + state: 'active', + } + this.SubscriptionLocator.promises.getUsersSubscription + .withArgs(this.user) + .onCall(0) + .resolves(this.individualSubscriptionWithoutPaymentState) + .withArgs(this.user) + .onCall(1) + .resolves(this.individualSubscription) + const payment = { + subscription: this.paymentRecord, + account: new PaymentProviderAccount({}), + coupons: [], + } + + this.Modules.promises.hooks.fire + .withArgs( + 'getPaymentFromRecordPromise', + this.individualSubscriptionWithoutPaymentState + ) + .resolves([payment]) + this.Modules.promises.hooks.fire + .withArgs( + 'syncSubscription', + payment, + this.individualSubscriptionWithoutPaymentState + ) + .resolves([]) + + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( + this.user + ) + + assert.deepEqual(usersBestSubscription, { + type: 'individual', + subscription: this.individualSubscription, + plan: this.plan, + remainingTrialDays: -1, + }) + assert.isTrue( + this.Modules.promises.hooks.fire.withArgs( + 'getPaymentFromRecordPromise', + this.individualSubscriptionWithoutPaymentState + ).calledOnce + ) }) }) @@ -318,8 +387,8 @@ describe('SubscriptionViewModelBuilder', function () { this.SubscriptionLocator.promises.getMemberSubscriptions .withArgs(this.user) .resolves([this.groupSubscription]) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) assert.deepEqual(usersBestSubscription, { @@ -336,8 +405,8 @@ describe('SubscriptionViewModelBuilder', function () { .resolves([ Object.assign({}, this.groupSubscription, { teamName: 'test team' }), ]) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) assert.deepEqual(usersBestSubscription, { @@ -353,8 +422,8 @@ describe('SubscriptionViewModelBuilder', function () { .withArgs(this.user._id) .resolves([this.commonsSubscription]) - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -385,8 +454,8 @@ describe('SubscriptionViewModelBuilder', function () { compileTimeout: 60, } - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -410,8 +479,8 @@ describe('SubscriptionViewModelBuilder', function () { compileTimeout: 60, } - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -440,8 +509,8 @@ describe('SubscriptionViewModelBuilder', function () { compileTimeout: 240, } - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -469,8 +538,8 @@ describe('SubscriptionViewModelBuilder', function () { compileTimeout: 240, } - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) @@ -499,8 +568,8 @@ describe('SubscriptionViewModelBuilder', function () { compileTimeout: 240, } - const usersBestSubscription = - await this.SubscriptionViewModelBuilder.promises.getBestSubscription( + const { bestSubscription: usersBestSubscription } = + await this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( this.user ) diff --git a/services/web/test/unit/src/Subscription/TeamInvitesController.test.mjs b/services/web/test/unit/src/Subscription/TeamInvitesController.test.mjs index b72a406ac0..87fc435a26 100644 --- a/services/web/test/unit/src/Subscription/TeamInvitesController.test.mjs +++ b/services/web/test/unit/src/Subscription/TeamInvitesController.test.mjs @@ -175,7 +175,7 @@ describe('TeamInvitesController', function () { }, } - describe('hasIndividualRecurlySubscription', function () { + describe('hasIndividualPaidSubscription', function () { it('is true for personal subscription', function (ctx) { return new Promise(resolve => { ctx.SubscriptionLocator.promises.getUsersSubscription.resolves({ @@ -184,7 +184,7 @@ describe('TeamInvitesController', function () { }) const res = { render: (template, data) => { - expect(data.hasIndividualRecurlySubscription).to.be.true + expect(data.hasIndividualPaidSubscription).to.be.true resolve() }, } @@ -200,7 +200,7 @@ describe('TeamInvitesController', function () { }) const res = { render: (template, data) => { - expect(data.hasIndividualRecurlySubscription).to.be.false + expect(data.hasIndividualPaidSubscription).to.be.false resolve() }, } @@ -219,7 +219,7 @@ describe('TeamInvitesController', function () { }) const res = { render: (template, data) => { - expect(data.hasIndividualRecurlySubscription).to.be.false + expect(data.hasIndividualPaidSubscription).to.be.false resolve() }, } diff --git a/services/web/types/project/dashboard/subscription.ts b/services/web/types/project/dashboard/subscription.ts index e8b595c49f..c8f8835b34 100644 --- a/services/web/types/project/dashboard/subscription.ts +++ b/services/web/types/project/dashboard/subscription.ts @@ -1,4 +1,7 @@ -import { SubscriptionState } from '../../subscription/dashboard/subscription' +import { + SubscriptionState, + PaymentProvider, +} from '../../subscription/dashboard/subscription' type SubscriptionBase = { featuresPageURL: string @@ -22,6 +25,7 @@ type PaidSubscriptionBase = { teamName?: string name: string recurlyStatus?: RecurlyStatus + paymentProvider?: PaymentProvider } } & SubscriptionBase diff --git a/services/web/types/subscription/dashboard/subscription.ts b/services/web/types/subscription/dashboard/subscription.ts index 92a61e8ddb..db17b25684 100644 --- a/services/web/types/subscription/dashboard/subscription.ts +++ b/services/web/types/subscription/dashboard/subscription.ts @@ -64,7 +64,6 @@ export type Subscription = { membersLimit: number teamInvites: object[] planCode: string - recurlySubscription_id: string plan: Plan pendingPlan?: PendingPaymentProviderPlan addOns?: AddOn[] diff --git a/services/web/types/user.ts b/services/web/types/user.ts index 8d00ea803f..2fce1ce46b 100644 --- a/services/web/types/user.ts +++ b/services/web/types/user.ts @@ -39,7 +39,7 @@ export type User = { isAdmin?: boolean email: string allowedFreeTrial?: boolean - hasRecurlySubscription?: boolean + hasPaidSubscription?: boolean first_name?: string last_name?: string alphaProgram?: boolean