(sub: {
id: string
plan: string | null
referenceId: string
seats?: number | null
periodStart?: Date | null
periodEnd?: Date | null
})
| 214 | * Shared logic between invoice.finalized and customer.subscription.deleted handlers |
| 215 | */ |
| 216 | export async function calculateSubscriptionOverage(sub: { |
| 217 | id: string |
| 218 | plan: string | null |
| 219 | referenceId: string |
| 220 | seats?: number | null |
| 221 | periodStart?: Date | null |
| 222 | periodEnd?: Date | null |
| 223 | }): Promise<number> { |
| 224 | // Enterprise plans have no overages |
| 225 | if (isEnterprise(sub.plan)) { |
| 226 | logger.info('Enterprise plan has no overages', { |
| 227 | subscriptionId: sub.id, |
| 228 | plan: sub.plan, |
| 229 | }) |
| 230 | return 0 |
| 231 | } |
| 232 | |
| 233 | let totalOverageDecimal = new Decimal(0) |
| 234 | |
| 235 | const isOrgScoped = await isSubscriptionOrgScoped(sub) |
| 236 | |
| 237 | if (isOrgScoped) { |
| 238 | const pooled = await aggregateOrgMemberStats(sub.referenceId) |
| 239 | const ledgerUsage = |
| 240 | sub.periodStart && sub.periodEnd |
| 241 | ? await getBillingPeriodUsageCost( |
| 242 | { type: 'organization', id: sub.referenceId }, |
| 243 | { start: sub.periodStart, end: sub.periodEnd } |
| 244 | ) |
| 245 | : 0 |
| 246 | |
| 247 | const orgData = await db |
| 248 | .select({ departedMemberUsage: organization.departedMemberUsage }) |
| 249 | .from(organization) |
| 250 | .where(eq(organization.id, sub.referenceId)) |
| 251 | .limit(1) |
| 252 | |
| 253 | const departedMemberUsage = |
| 254 | orgData.length > 0 ? toNumber(toDecimal(orgData[0].departedMemberUsage)) : 0 |
| 255 | |
| 256 | const { totalOverage, effectiveUsage, baseSubscriptionAmount } = await computeOrgOverageAmount({ |
| 257 | plan: sub.plan, |
| 258 | seats: sub.seats ?? null, |
| 259 | periodStart: sub.periodStart ?? null, |
| 260 | periodEnd: sub.periodEnd ?? null, |
| 261 | organizationId: sub.referenceId, |
| 262 | pooledCurrentPeriodCost: pooled.currentPeriodCost + ledgerUsage, |
| 263 | departedMemberUsage, |
| 264 | memberIds: pooled.memberIds, |
| 265 | }) |
| 266 | |
| 267 | totalOverageDecimal = toDecimal(totalOverage) |
| 268 | |
| 269 | logger.info('Calculated org-scoped overage', { |
| 270 | subscriptionId: sub.id, |
| 271 | plan: sub.plan, |
| 272 | currentMemberUsage: pooled.currentPeriodCost + ledgerUsage, |
| 273 | departedMemberUsage, |
no test coverage detected