mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-25 18:20:09 +02:00
* feat: add ability to set restore point for subscriptions * feat: update recurly client with ability to get past due invoices and fail invoices * utility to retrieve last valid subscription * create revert requests and fail invoices, revert subscriptions to previous valid states on failed upgrade payments * add restore point and call to revert plans on failed payments * code style for PaymentProviderEntities * moving subs restore point check to SubscriptionController, and removing unecessary error * adding ability to stop sub restores without a deploy * ensure that subs restore point is set before changing plan * changing reverted flag on subscription to count, and only reverting automatic invoices * updating tests with restorepoint functions * rethrow error after voiding restore point, and ensure that recurly failed_payment always gets a 200 response * only void restore point if the changeRequest fails GitOrigin-RevId: cf3074c13db22d1cf680b59c4d57817c390db23e
171 lines
5.1 KiB
JavaScript
171 lines
5.1 KiB
JavaScript
/**
|
|
* @import { AddOn } from '../../../../types/subscription/plan'
|
|
*/
|
|
|
|
const { callbackifyAll } = require('@overleaf/promise-utils')
|
|
const { Subscription } = require('../../models/Subscription')
|
|
const { DeletedSubscription } = require('../../models/DeletedSubscription')
|
|
const logger = require('@overleaf/logger')
|
|
const {
|
|
AI_ADD_ON_CODE,
|
|
isStandaloneAiAddOnPlanCode,
|
|
} = require('./PaymentProviderEntities')
|
|
require('./GroupPlansData') // make sure dynamic group plans are loaded
|
|
|
|
const SubscriptionLocator = {
|
|
async getUsersSubscription(userOrId) {
|
|
const userId = SubscriptionLocator._getUserId(userOrId)
|
|
const subscription = await Subscription.findOne({ admin_id: userId }).exec()
|
|
logger.debug({ userId }, 'got users subscription')
|
|
return subscription
|
|
},
|
|
|
|
async getUserIndividualSubscription(userOrId) {
|
|
const userId = SubscriptionLocator._getUserId(userOrId)
|
|
const subscription = await Subscription.findOne({
|
|
admin_id: userId,
|
|
groupPlan: false,
|
|
}).exec()
|
|
logger.debug({ userId }, 'got users individual subscription')
|
|
return subscription
|
|
},
|
|
|
|
async getManagedGroupSubscriptions(userOrId) {
|
|
return await Subscription.find({
|
|
manager_ids: userOrId,
|
|
groupPlan: true,
|
|
})
|
|
.populate('admin_id', ['_id', 'email'])
|
|
.exec()
|
|
},
|
|
|
|
async getMemberSubscriptions(userOrId, populate = []) {
|
|
const userId = SubscriptionLocator._getUserId(userOrId)
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
return await Subscription.find({ member_ids: userId })
|
|
.populate('admin_id', 'email')
|
|
.populate(populate)
|
|
.exec()
|
|
},
|
|
|
|
async getAdminEmail(subscriptionId) {
|
|
const subscription = await Subscription.findById(subscriptionId)
|
|
.populate('admin_id', 'email')
|
|
.exec()
|
|
|
|
return subscription?.admin_id?.email
|
|
},
|
|
|
|
async getAdminEmailAndName(subscriptionId) {
|
|
const subscription = await Subscription.findById(subscriptionId)
|
|
.populate('admin_id', ['email', 'first_name', 'last_name'])
|
|
.exec()
|
|
|
|
return subscription?.admin_id
|
|
},
|
|
|
|
async hasRecurlyGroupSubscription(userOrId) {
|
|
const userId = SubscriptionLocator._getUserId(userOrId)
|
|
return await Subscription.exists({
|
|
groupPlan: true,
|
|
recurlySubscription_id: { $exists: true },
|
|
$or: [
|
|
{ member_ids: userId },
|
|
{ manager_ids: userId },
|
|
{ admin_id: userId },
|
|
],
|
|
}).exec()
|
|
},
|
|
|
|
async getSubscription(subscriptionId) {
|
|
return await Subscription.findOne({ _id: subscriptionId }).exec()
|
|
},
|
|
|
|
async getSubscriptionByMemberIdAndId(userId, subscriptionId) {
|
|
return await Subscription.findOne(
|
|
{ member_ids: userId, _id: subscriptionId },
|
|
{ _id: 1 }
|
|
).exec()
|
|
},
|
|
|
|
async getGroupSubscriptionsMemberOf(userId) {
|
|
return await Subscription.find(
|
|
{ member_ids: userId },
|
|
{ _id: 1, planCode: 1 }
|
|
)
|
|
},
|
|
|
|
async getGroupsWithEmailInvite(email) {
|
|
return await Subscription.find({ invited_emails: email }).exec()
|
|
},
|
|
|
|
async getGroupsWithTeamInvitesEmail(email) {
|
|
return await Subscription.find(
|
|
{ teamInvites: { $elemMatch: { email } } },
|
|
{ teamInvites: 1 }
|
|
).exec()
|
|
},
|
|
|
|
async getGroupWithV1Id(v1TeamId) {
|
|
return await Subscription.findOne({ 'overleaf.id': v1TeamId }).exec()
|
|
},
|
|
|
|
async getUserDeletedSubscriptions(userId) {
|
|
return await DeletedSubscription.find({
|
|
'subscription.admin_id': userId,
|
|
}).exec()
|
|
},
|
|
|
|
async getDeletedSubscription(subscriptionId) {
|
|
return await DeletedSubscription.findOne({
|
|
'subscription._id': subscriptionId,
|
|
}).exec()
|
|
},
|
|
|
|
async hasAiAssist(userOrId) {
|
|
const userId = SubscriptionLocator._getUserId(userOrId)
|
|
const subscription = await Subscription.findOne({ admin_id: userId }).exec()
|
|
// todo: as opposed to recurlyEntities which use addon.code, subscription model uses addon.addOnCode
|
|
// which we hope to align via https://github.com/overleaf/internal/issues/25494
|
|
return Boolean(
|
|
(subscription?.planCode &&
|
|
isStandaloneAiAddOnPlanCode(subscription?.planCode)) ||
|
|
subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE)
|
|
)
|
|
},
|
|
|
|
_getUserId(userOrId) {
|
|
if (userOrId && userOrId._id) {
|
|
return userOrId._id
|
|
} else if (userOrId) {
|
|
return userOrId
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Retrieves the last successful subscription for a given user.
|
|
*
|
|
* @async
|
|
* @function
|
|
* @param {string} recurlyId - The ID of the recurly subscription tied to the mongo subscription to check for a previous successful state.
|
|
* @returns {Promise<{_id: ObjectId, planCode: string, addOns: [AddOn]}|null>} A promise that resolves to the last successful planCode and addon state,
|
|
* or null if we havent stored a previous
|
|
*/
|
|
async getLastSuccessfulSubscription(recurlyId) {
|
|
const subscription = await Subscription.findOne({
|
|
recurlySubscription_id: recurlyId,
|
|
}).exec()
|
|
return subscription && subscription.lastSuccesfulSubscription
|
|
? {
|
|
...subscription.lastSuccesfulSubscription,
|
|
_id: subscription._id,
|
|
}
|
|
: null
|
|
},
|
|
}
|
|
|
|
module.exports = {
|
|
...callbackifyAll(SubscriptionLocator),
|
|
promises: SubscriptionLocator,
|
|
}
|