Skip to content

Commit 3181f77

Browse files
committed
fix idempotency key issue
1 parent 8255a70 commit 3181f77

2 files changed

Lines changed: 29 additions & 20 deletions

File tree

apps/sim/lib/billing/threshold-billing.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { db } from '@sim/db'
22
import { member, subscription, userStats } from '@sim/db/schema'
3-
import { and, eq, sql } from 'drizzle-orm'
3+
import { and, eq, inArray, sql } from 'drizzle-orm'
44
import type Stripe from 'stripe'
55
import { DEFAULT_OVERAGE_THRESHOLD } from '@/lib/billing/constants'
66
import { calculateSubscriptionOverage, getPlanPricing } from '@/lib/billing/core/billing'
@@ -178,8 +178,9 @@ export async function checkAndBillOverageThreshold(userId: string): Promise<void
178178
: Math.floor(Date.now() / 1000)
179179
const billingPeriod = new Date(periodEnd * 1000).toISOString().slice(0, 7)
180180

181-
const timestamp = Date.now()
182-
const idempotencyKey = `threshold-overage:${customerId}:${stripeSubscriptionId}:${billingPeriod}:${timestamp}`
181+
const amountCents = Math.round(amountToBill * 100)
182+
const totalOverageCents = Math.round(currentOverage * 100)
183+
const idempotencyKey = `threshold-overage:${customerId}:${stripeSubscriptionId}:${billingPeriod}:${totalOverageCents}:${amountCents}`
183184

184185
logger.info('Creating threshold overage invoice', {
185186
userId,
@@ -190,7 +191,7 @@ export async function checkAndBillOverageThreshold(userId: string): Promise<void
190191
})
191192

192193
try {
193-
const cents = Math.round(amountToBill * 100)
194+
const cents = amountCents
194195

195196
const invoiceId = await createAndFinalizeOverageInvoice(stripe, {
196197
customerId,
@@ -199,7 +200,7 @@ export async function checkAndBillOverageThreshold(userId: string): Promise<void
199200
description: `Threshold overage billing – ${billingPeriod}`,
200201
itemDescription: `Usage overage ($${amountToBill.toFixed(2)})`,
201202
metadata: {
202-
type: 'threshold_overage',
203+
type: 'overage_threshold_billing',
203204
userId,
204205
subscriptionId: stripeSubscriptionId,
205206
billingPeriod,
@@ -288,22 +289,22 @@ export async function checkAndBillOrganizationOverageThreshold(
288289
return
289290
}
290291

291-
let totalTeamUsage = 0
292-
let totalBilledOverage = 0
292+
let totalTeamUsage = parseDecimal(ownerStatsLock[0].currentPeriodCost)
293+
const totalBilledOverage = parseDecimal(ownerStatsLock[0].billedOverageThisPeriod)
293294

294-
for (const m of members) {
295-
const memberStats = await tx
295+
const nonOwnerIds = members.filter((m) => m.userId !== owner.userId).map((m) => m.userId)
296+
297+
if (nonOwnerIds.length > 0) {
298+
const memberStatsRows = await tx
296299
.select({
300+
userId: userStats.userId,
297301
currentPeriodCost: userStats.currentPeriodCost,
298-
billedOverageThisPeriod: userStats.billedOverageThisPeriod,
299302
})
300303
.from(userStats)
301-
.where(eq(userStats.userId, m.userId))
302-
.limit(1)
304+
.where(inArray(userStats.userId, nonOwnerIds))
303305

304-
if (memberStats.length > 0) {
305-
totalTeamUsage += parseDecimal(memberStats[0].currentPeriodCost)
306-
totalBilledOverage += parseDecimal(memberStats[0].billedOverageThisPeriod)
306+
for (const stats of memberStatsRows) {
307+
totalTeamUsage += parseDecimal(stats.currentPeriodCost)
307308
}
308309
}
309310

@@ -342,9 +343,10 @@ export async function checkAndBillOrganizationOverageThreshold(
342343
? Math.floor(orgSubscription.periodEnd.getTime() / 1000)
343344
: Math.floor(Date.now() / 1000)
344345
const billingPeriod = new Date(periodEnd * 1000).toISOString().slice(0, 7)
345-
const timestamp = Date.now()
346+
const amountCents = Math.round(amountToBill * 100)
347+
const totalOverageCents = Math.round(currentOverage * 100)
346348

347-
const idempotencyKey = `threshold-overage-org:${customerId}:${stripeSubscriptionId}:${billingPeriod}:${timestamp}`
349+
const idempotencyKey = `threshold-overage-org:${customerId}:${stripeSubscriptionId}:${billingPeriod}:${totalOverageCents}:${amountCents}`
348350

349351
logger.info('Creating organization threshold overage invoice', {
350352
organizationId,
@@ -353,7 +355,7 @@ export async function checkAndBillOrganizationOverageThreshold(
353355
})
354356

355357
try {
356-
const cents = Math.round(amountToBill * 100)
358+
const cents = amountCents
357359

358360
const invoiceId = await createAndFinalizeOverageInvoice(stripe, {
359361
customerId,
@@ -362,7 +364,7 @@ export async function checkAndBillOrganizationOverageThreshold(
362364
description: `Team threshold overage billing – ${billingPeriod}`,
363365
itemDescription: `Team usage overage ($${amountToBill.toFixed(2)})`,
364366
metadata: {
365-
type: 'threshold_overage_organization',
367+
type: 'overage_threshold_billing_org',
366368
organizationId,
367369
subscriptionId: stripeSubscriptionId,
368370
billingPeriod,

apps/sim/lib/billing/webhooks/invoices.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { createLogger } from '@/lib/logs/console/logger'
88

99
const logger = createLogger('StripeInvoiceWebhooks')
1010

11+
const OVERAGE_INVOICE_TYPES = new Set<string>([
12+
'overage_billing',
13+
'overage_threshold_billing',
14+
'overage_threshold_billing_org',
15+
])
16+
1117
function parseDecimal(value: string | number | null | undefined): number {
1218
if (value === null || value === undefined) return 0
1319
return Number.parseFloat(value.toString())
@@ -196,7 +202,8 @@ export async function handleInvoicePaymentFailed(event: Stripe.Event) {
196202
try {
197203
const invoice = event.data.object as Stripe.Invoice
198204

199-
const isOverageInvoice = invoice.metadata?.type === 'overage_billing'
205+
const invoiceType = invoice.metadata?.type
206+
const isOverageInvoice = !!(invoiceType && OVERAGE_INVOICE_TYPES.has(invoiceType))
200207
let stripeSubscriptionId: string | undefined
201208

202209
if (isOverageInvoice) {

0 commit comments

Comments
 (0)