Merge pull request #24523 from overleaf/jdt-prevent-bundle-dbl-buys

Redirect bundle purchases when users already have it

GitOrigin-RevId: d8e3c0256db08c08c2be24f38caef91fb26b90e8
This commit is contained in:
Jimmy Domagala-Tang
2025-04-10 08:06:28 +00:00
committed by Copybot
parent f11a6a6b87
commit f7f4a03abb
6 changed files with 65 additions and 6 deletions
@@ -197,6 +197,14 @@ async function doSyncFromV1(v1UserId) {
return refreshFeatures(user._id, 'sync-v1')
}
async function hasFeaturesViaWritefull(userId) {
const user = await UserGetter.promises.getUser(userId, {
_id: 1,
writefull: 1,
})
return Boolean(user?.writefull?.isPremium)
}
module.exports = {
featuresEpochIsCurrent,
computeFeatures: callbackify(computeFeatures),
@@ -209,10 +217,12 @@ module.exports = {
'featuresChanged',
]),
scheduleRefreshFeatures: callbackify(scheduleRefreshFeatures),
hasFeaturesViaWritefull: callbackify(hasFeaturesViaWritefull),
promises: {
computeFeatures,
refreshFeatures,
scheduleRefreshFeatures,
doSyncFromV1,
hasFeaturesViaWritefull,
},
}
@@ -477,6 +477,22 @@ function isStandaloneAiAddOnPlanCode(planCode) {
return STANDALONE_AI_ADD_ON_CODES.includes(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
*
* @return {boolean}
*/
function subscriptionChangeIsAiAssistUpgrade(subscriptionChange) {
return Boolean(
isStandaloneAiAddOnPlanCode(subscriptionChange.nextPlanCode) ||
subscriptionChange.nextAddOns?.some(
addOn => addOn.code === AI_ADD_ON_CODE
)
)
}
module.exports = {
AI_ADD_ON_CODE,
MEMBERS_LIMIT_ADD_ON_CODE,
@@ -493,5 +509,6 @@ module.exports = {
RecurlyCoupon,
RecurlyAccount,
isStandaloneAiAddOnPlanCode,
subscriptionChangeIsAiAssistUpgrade,
RecurlyImmediateCharge,
}
@@ -24,6 +24,7 @@ const HttpErrorHandler = require('../Errors/HttpErrorHandler')
const RecurlyClient = require('./RecurlyClient')
const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
const PlansLocator = require('./PlansLocator')
const RecurlyEntities = require('./RecurlyEntities')
/**
* @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview'
@@ -45,7 +46,6 @@ function formatGroupPlansDataForDash() {
async function userSubscriptionPage(req, res) {
const user = SessionManager.getSessionUser(req.session)
await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription')
const groupPricingDiscount = await SplitTestHandler.promises.getAssignment(
@@ -321,13 +321,19 @@ async function previewAddonPurchase(req, res) {
try {
subscriptionChange =
await SubscriptionHandler.promises.previewAddonPurchase(userId, addOnCode)
const hasBundleViaWritefull =
await FeaturesUpdater.promises.hasFeaturesViaWritefull(userId)
const isAiUpgrade =
RecurlyEntities.subscriptionChangeIsAiAssistUpgrade(subscriptionChange)
if (hasBundleViaWritefull && isAiUpgrade) {
return res.redirect(
'/user/subscription?redirect-reason=writefull-entitled'
)
}
} catch (err) {
if (err instanceof DuplicateAddOnError) {
return HttpErrorHandler.badRequest(
req,
res,
`Subscription already has add-on "${addOnCode}"`
)
return res.redirect('/user/subscription?redirect-reason=double-buy')
}
throw err
}