diff --git a/packages/server/src/IdentityManager.ts b/packages/server/src/IdentityManager.ts
index 09e9199c4..d7b26597e 100644
--- a/packages/server/src/IdentityManager.ts
+++ b/packages/server/src/IdentityManager.ts
@@ -321,7 +321,7 @@ export class IdentityManager {
return await this.stripeManager.getAdditionalSeatsProration(subscriptionId, newQuantity)
}
- public async updateAdditionalSeats(subscriptionId: string, quantity: number, prorationDate: number) {
+ public async updateAdditionalSeats(subscriptionId: string, quantity: number, prorationDate: number, increase: boolean) {
if (!subscriptionId) return {}
if (!this.stripeManager) {
@@ -330,7 +330,8 @@ export class IdentityManager {
const { success, subscription, invoice, paymentFailed, paymentError } = await this.stripeManager.updateAdditionalSeats(
subscriptionId,
quantity,
- prorationDate
+ prorationDate,
+ increase
)
// Fetch product details to get quotas
diff --git a/packages/server/src/StripeManager.ts b/packages/server/src/StripeManager.ts
index dabc2519e..5c80b7260 100644
--- a/packages/server/src/StripeManager.ts
+++ b/packages/server/src/StripeManager.ts
@@ -3,6 +3,9 @@ import { Request } from 'express'
import { UsageCacheManager } from './UsageCacheManager'
import { UserPlan } from './Interface'
import { LICENSE_QUOTAS } from './utils/constants'
+import { InternalFlowiseError } from './errors/internalFlowiseError'
+import { StatusCodes } from 'http-status-codes'
+import logger from './utils/logger'
export class StripeManager {
private static instance: StripeManager
@@ -359,33 +362,43 @@ export class StripeManager {
}
}
- public async updateAdditionalSeats(subscriptionId: string, quantity: number, _prorationDate: number) {
+ public async updateAdditionalSeats(subscriptionId: string, quantity: number, _prorationDate: number, increase: boolean) {
if (!this.stripe) {
throw new Error('Stripe is not initialized')
}
try {
- const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
- const additionalSeatsItem = subscription.items.data.find(
- (item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
- )
-
// Get the price ID for additional seats if needed
const prices = await this.stripe.prices.list({
product: process.env.ADDITIONAL_SEAT_ID,
active: true,
limit: 1
})
-
if (prices.data.length === 0) {
throw new Error('No active price found for additional seats')
}
+ const openInvoices = await this.stripe.invoices.list({
+ subscription: subscriptionId,
+ status: 'open'
+ })
+ const openAdditionalSeatsInvoices = openInvoices.data.filter((invoice) =>
+ invoice.lines?.data?.some((line) => line.price?.id === prices.data[0].id)
+ )
+ logger.info(`openAdditionalSeatsInvoices: ${openAdditionalSeatsInvoices.length}, increase: ${increase}`)
+ if (openAdditionalSeatsInvoices.length > 0 && increase === true)
+ throw new InternalFlowiseError(StatusCodes.PAYMENT_REQUIRED, "Not allow to add seats when there're unsuccessful payment")
+
+ const subscription = await this.stripe.subscriptions.retrieve(subscriptionId)
+ const additionalSeatsItem = subscription.items.data.find(
+ (item) => (item.price.product as string) === process.env.ADDITIONAL_SEAT_ID
+ )
+
// TODO: Fix proration date for sandbox testing - use subscription period bounds
const adjustedProrationDate = this.calculateSafeProrationDate(subscription)
// Create an invoice immediately for the proration
- const updatedSubscription = await this.stripe.subscriptions.update(subscriptionId, {
+ const subscriptionUpdateData: any = {
items: [
additionalSeatsItem
? {
@@ -396,10 +409,16 @@ export class StripeManager {
price: prices.data[0].id,
quantity: quantity
}
- ],
- proration_behavior: 'always_invoice',
- proration_date: adjustedProrationDate
- })
+ ]
+ }
+ if (openAdditionalSeatsInvoices.length > 0) {
+ await this.stripe.invoices.voidInvoice(openAdditionalSeatsInvoices[0].id)
+ subscriptionUpdateData.proration_behavior = 'none'
+ } else {
+ ;(subscriptionUpdateData.proration_behavior = 'always_invoice'),
+ (subscriptionUpdateData.proration_date = adjustedProrationDate)
+ }
+ const updatedSubscription = await this.stripe.subscriptions.update(subscriptionId, subscriptionUpdateData)
// Get the latest invoice for this subscription
const invoice = await this.stripe.invoices.list({
diff --git a/packages/server/src/enterprise/controllers/organization.controller.ts b/packages/server/src/enterprise/controllers/organization.controller.ts
index fc57184dd..de11fa816 100644
--- a/packages/server/src/enterprise/controllers/organization.controller.ts
+++ b/packages/server/src/enterprise/controllers/organization.controller.ts
@@ -134,7 +134,7 @@ export class OrganizationController {
public async updateAdditionalSeats(req: Request, res: Response, next: NextFunction) {
try {
- const { subscriptionId, quantity, prorationDate } = req.body
+ const { subscriptionId, quantity, prorationDate, increase } = req.body
if (!subscriptionId) {
return res.status(400).json({ error: 'Subscription ID is required' })
}
@@ -144,8 +144,10 @@ export class OrganizationController {
if (!prorationDate) {
return res.status(400).json({ error: 'Proration date is required' })
}
+ if (increase === undefined) return res.status(StatusCodes.BAD_REQUEST).json({ error: 'Increase is required' })
+
const identityManager = getRunningExpressApp().identityManager
- const result = await identityManager.updateAdditionalSeats(subscriptionId, quantity, prorationDate)
+ const result = await identityManager.updateAdditionalSeats(subscriptionId, quantity, prorationDate, increase)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
diff --git a/packages/ui/src/api/user.js b/packages/ui/src/api/user.js
index 86165ec9c..cc5ff2992 100644
--- a/packages/ui/src/api/user.js
+++ b/packages/ui/src/api/user.js
@@ -18,8 +18,8 @@ const getAdditionalSeatsQuantity = (subscriptionId) =>
const getCustomerDefaultSource = (customerId) => client.get(`/organization/customer-default-source?customerId=${customerId}`)
const getAdditionalSeatsProration = (subscriptionId, quantity) =>
client.get(`/organization/additional-seats-proration?subscriptionId=${subscriptionId}&quantity=${quantity}`)
-const updateAdditionalSeats = (subscriptionId, quantity, prorationDate) =>
- client.post(`/organization/update-additional-seats`, { subscriptionId, quantity, prorationDate })
+const updateAdditionalSeats = (subscriptionId, quantity, prorationDate, increase) =>
+ client.post(`/organization/update-additional-seats`, { subscriptionId, quantity, prorationDate, increase })
const getPlanProration = (subscriptionId, newPlanId) =>
client.get(`/organization/plan-proration?subscriptionId=${subscriptionId}&newPlanId=${newPlanId}`)
const updateSubscriptionPlan = (subscriptionId, newPlanId, prorationDate) =>
diff --git a/packages/ui/src/views/account/index.jsx b/packages/ui/src/views/account/index.jsx
index 2236e1aed..cd4a57679 100644
--- a/packages/ui/src/views/account/index.jsx
+++ b/packages/ui/src/views/account/index.jsx
@@ -347,7 +347,7 @@ const AccountSettings = () => {
}
}
- const handleSeatsModification = async (newSeatsAmount) => {
+ const handleSeatsModification = async (newSeatsAmount, increase) => {
try {
setIsUpdatingSeats(true)
@@ -358,7 +358,8 @@ const AccountSettings = () => {
const response = await updateAdditionalSeatsApi.request(
currentUser?.activeOrganizationSubscriptionId,
newSeatsAmount,
- prorationInfo.prorationDate
+ prorationInfo.prorationDate,
+ increase
)
// Check if payment failed but seats were updated (Issue #4 fix)
@@ -1181,7 +1182,7 @@ const AccountSettings = () => {