Merge pull request #22032 from overleaf/em-subscription-change-preview-404

Handle user without a subscription when asking for a change preview

GitOrigin-RevId: dab90720b292fd0cfb6dbb8a554797b9d2ac436b
This commit is contained in:
Eric Mc Sween 2024-11-22 08:09:40 -05:00 committed by Copybot
parent 838ae23b52
commit b0c309e993
3 changed files with 52 additions and 48 deletions

View file

@ -14,11 +14,8 @@ class DuplicateAddOnError extends OError {}
class AddOnNotPresentError extends OError {}
class NoRecurlySubscriptionError extends OError {}
module.exports = {
RecurlyTransactionError,
DuplicateAddOnError,
AddOnNotPresentError,
NoRecurlySubscriptionError,
}

View file

@ -547,7 +547,11 @@ async function purchaseAddon(req, res, next) {
logger.debug({ userId: user._id, addOnCode }, 'purchasing add-ons')
try {
await SubscriptionHandler.promises.purchaseAddon(user, addOnCode, quantity)
await SubscriptionHandler.promises.purchaseAddon(
user._id,
addOnCode,
quantity
)
return res.sendStatus(200)
} catch (err) {
if (err instanceof DuplicateAddOnError) {
@ -580,7 +584,7 @@ async function removeAddon(req, res, next) {
logger.debug({ userId: user._id, addOnCode }, 'removing add-ons')
try {
await SubscriptionHandler.promises.removeAddon(user, addOnCode)
await SubscriptionHandler.promises.removeAddon(user._id, addOnCode)
res.sendStatus(200)
} catch (err) {
if (err instanceof AddOnNotPresentError) {

View file

@ -11,7 +11,6 @@ const LimitationsManager = require('./LimitationsManager')
const EmailHandler = require('../Email/EmailHandler')
const { callbackify } = require('@overleaf/promise-utils')
const UserUpdater = require('../User/UserUpdater')
const { NoRecurlySubscriptionError } = require('./Errors')
const { NotFoundError } = require('../Errors/Errors')
/**
@ -73,12 +72,7 @@ async function createSubscription(user, subscriptionDetails, recurlyTokenIds) {
* @return {Promise<RecurlySubscriptionChange>}
*/
async function previewSubscriptionChange(userId, planCode) {
const recurlyId = await getSubscriptionRecurlyId(userId)
if (recurlyId == null) {
throw new NoRecurlySubscriptionError('Subscription not found', { userId })
}
const subscription = await RecurlyClient.promises.getSubscription(recurlyId)
const subscription = await getSubscriptionForUser(userId)
const changeRequest = subscription?.getRequestForPlanChange(planCode)
const change =
await RecurlyClient.promises.previewSubscriptionChange(changeRequest)
@ -284,26 +278,6 @@ async function _updateSubscriptionFromRecurly(subscription) {
)
}
/**
* @return {Promise<RecurlySubscription>}
*/
async function _getSubscription(user) {
const { hasSubscription = false, subscription } =
await LimitationsManager.promises.userHasV2Subscription(user)
if (!hasSubscription || !subscription?.recurlySubscription_id) {
throw new NoRecurlySubscriptionError(
"could not fetch the user's Recurly subscription",
{ userId: user._id }
)
}
const currentSub = await RecurlyClient.promises.getSubscription(
subscription.recurlySubscription_id
)
return currentSub
}
/**
* Preview the effect of purchasing an add-on
*
@ -312,12 +286,8 @@ async function _getSubscription(user) {
* @return {Promise<RecurlySubscriptionChange>}
*/
async function previewAddonPurchase(userId, addOnCode) {
const recurlyId = await getSubscriptionRecurlyId(userId)
if (recurlyId == null) {
throw new NoRecurlySubscriptionError('Subscription not found', { userId })
}
const subscription = await getSubscriptionForUser(userId)
const subscription = await RecurlyClient.promises.getSubscription(recurlyId)
try {
await RecurlyClient.promises.getAddOn(subscription.planCode, addOnCode)
} catch (err) {
@ -329,14 +299,22 @@ async function previewAddonPurchase(userId, addOnCode) {
}
throw err
}
const changeRequest = subscription.getRequestForAddOnPurchase(addOnCode)
const change =
await RecurlyClient.promises.previewSubscriptionChange(changeRequest)
return change
}
async function purchaseAddon(user, addOnCode, quantity) {
const subscription = await _getSubscription(user)
/**
* Purchase an add-on for a user
*
* @param {string} userId
* @param {string} addOnCode
* @param {number} quantity
*/
async function purchaseAddon(userId, addOnCode, quantity) {
const subscription = await getSubscriptionForUser(userId)
try {
await RecurlyClient.promises.getAddOn(subscription.planCode, addOnCode)
} catch (err) {
@ -353,29 +331,54 @@ async function purchaseAddon(user, addOnCode, quantity) {
quantity
)
await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest)
await syncSubscription({ uuid: subscription.id }, user._id)
await syncSubscription({ uuid: subscription.id }, userId)
}
async function removeAddon(user, addOnCode) {
const subscription = await _getSubscription(user)
/**
* Cancels and add-on for a user
*
* @param {string} userId
* @param {string} addOnCode
*/
async function removeAddon(userId, addOnCode) {
const subscription = await getSubscriptionForUser(userId)
const changeRequest = subscription.getRequestForAddOnRemoval(addOnCode)
await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest)
await syncSubscription({ uuid: subscription.id }, user._id)
await syncSubscription({ uuid: subscription.id }, userId)
}
/**
* Returns the Recurly UUID for the given user
*
* Throws a NotFoundError if the subscription can't be found
*
* @param {string} userId
* @return {Promise<string | null>} the Recurly UUID
* @return {Promise<RecurlySubscription>}
*/
async function getSubscriptionRecurlyId(userId) {
async function getSubscriptionForUser(userId) {
const subscription =
await SubscriptionLocator.promises.getUsersSubscription(userId)
if (subscription == null) {
return null
const recurlyId = subscription?.recurlySubscription_id
if (recurlyId == null) {
throw new NotFoundError({
message: 'Recurly subscription not found',
info: { userId },
})
}
try {
const subscription = await RecurlyClient.promises.getSubscription(recurlyId)
return subscription
} catch (err) {
if (err instanceof recurly.errors.NotFoundError) {
throw new NotFoundError({
message: 'Subscription not found',
info: { userId, recurlyId },
})
} else {
throw err
}
}
return subscription.recurlySubscription_id ?? null
}
module.exports = {