Merge pull request #23203 from overleaf/ii-flexible-group-licensing-no-billing-details

[web] FL handle subscriptions with missing billing info

GitOrigin-RevId: 34209299c039992a80da5739e086beb5d0ede7b0
This commit is contained in:
ilkin-overleaf
2025-02-04 13:53:14 +02:00
committed by Copybot
parent 72be034435
commit 16130b79db
14 changed files with 242 additions and 2 deletions

View File

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

View File

@@ -16,6 +16,7 @@ const {
RecurlyPlan,
RecurlyImmediateCharge,
} = require('./RecurlyEntities')
const { MissingBillingInfoError } = require('./Errors')
/**
* @import { RecurlySubscriptionChangeRequest } from './RecurlyEntities'
@@ -193,7 +194,19 @@ async function resumeSubscriptionByUuid(subscriptionUuid) {
* @return {Promise<PaymentMethod>}
*/
async function getPaymentMethod(userId) {
const billingInfo = await client.getBillingInfo(`code-${userId}`)
let billingInfo
try {
billingInfo = await client.getBillingInfo(`code-${userId}`)
} catch (error) {
if (error instanceof recurly.errors.NotFoundError) {
throw new MissingBillingInfoError('This account has no billing info', {
userId,
})
}
throw error
}
return paymentMethodFromApi(billingInfo)
}

View File

@@ -13,6 +13,8 @@ import ErrorController from '../Errors/ErrorController.js'
import UserGetter from '../User/UserGetter.js'
import { Subscription } from '../../models/Subscription.js'
import { isProfessionalGroupPlan } from './PlansHelper.mjs'
import { MissingBillingInfoError } from './Errors.js'
import RecurlyClient from './RecurlyClient.js'
/**
* @import { Subscription } from "../../../../types/subscription/dashboard/subscription.js"
@@ -129,6 +131,8 @@ async function addSeatsToGroupSubscription(req, res) {
userId
)
await SubscriptionGroupHandler.promises.ensureFlexibleLicensingEnabled(plan)
// Check if the user has missing billing details
await RecurlyClient.promises.getPaymentMethod(userId)
res.render('subscriptions/add-seats', {
subscriptionId: subscription._id,
@@ -141,6 +145,13 @@ async function addSeatsToGroupSubscription(req, res) {
{ error },
'error while getting users group subscription details'
)
if (error instanceof MissingBillingInfoError) {
return res.redirect(
'/user/subscription/group/missing-billing-information'
)
}
return res.redirect('/user/subscription')
}
}
@@ -247,6 +258,13 @@ async function subscriptionUpgradePage(req, res) {
})
} catch (error) {
logger.err({ error }, 'error loading upgrade subscription page')
if (error instanceof MissingBillingInfoError) {
return res.redirect(
'/user/subscription/group/missing-billing-information'
)
}
return res.redirect('/user/subscription')
}
}
@@ -262,6 +280,24 @@ async function upgradeSubscription(req, res) {
}
}
async function missingBillingInformation(req, res) {
try {
const userId = SessionManager.getLoggedInUserId(req.session)
const subscription =
await SubscriptionLocator.promises.getUsersSubscription(userId)
res.render('subscriptions/missing-billing-information', {
groupName: subscription.teamName,
})
} catch (error) {
logger.err(
{ error },
'error trying to render missing billing information page'
)
return res.render('/user/subscription')
}
}
export default {
removeUserFromGroup: expressify(removeUserFromGroup),
removeSelfFromGroup: expressify(removeSelfFromGroup),
@@ -276,4 +312,5 @@ export default {
),
subscriptionUpgradePage: expressify(subscriptionUpgradePage),
upgradeSubscription: expressify(upgradeSubscription),
missingBillingInformation: expressify(missingBillingInformation),
}

View File

@@ -81,6 +81,7 @@ async function getUsersGroupSubscriptionDetails(userId) {
)
return {
userId,
subscription,
recurlySubscription,
plan,

View File

@@ -119,6 +119,14 @@ export default {
SubscriptionGroupController.upgradeSubscription
)
webRouter.get(
'/user/subscription/group/missing-billing-information',
AuthenticationController.requireLogin(),
RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
SubscriptionGroupController.flexibleLicensingSplitTest,
SubscriptionGroupController.missingBillingInformation
)
// Team invites
webRouter.get(
'/subscription/invites/:token/',