Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
aeef2b7
v0.3.19: openai oss models, invite & search modal fixes
waleedlatif1 Aug 5, 2025
9f2ff7e
Merge pull request #883 from simstudioai/staging
icecrasher321 Aug 5, 2025
85cdca2
v0.3.21: gpt-5, copilot files, configurable rate limits, fix deployed…
waleedlatif1 Aug 7, 2025
aedf5e7
v0.3.22: handle files, trigger mode, email validation, tag dropdown t…
waleedlatif1 Aug 9, 2025
1c818b2
v0.3.23: multiplayer variables, api key fixes, kb improvements, trigg…
icecrasher321 Aug 12, 2025
8fccd5c
Merge pull request #948 from simstudioai/staging
icecrasher321 Aug 12, 2025
f7573fa
v0.3.24: api block fixes
waleedlatif1 Aug 13, 2025
4107948
Merge pull request #954 from simstudioai/staging
waleedlatif1 Aug 13, 2025
56ffb53
Merge pull request #964 from simstudioai/staging
icecrasher321 Aug 14, 2025
e1f04f4
v0.3.26: fix billing, bubble up workflow block errors, credentials se…
icecrasher321 Aug 14, 2025
6133db5
v0.3.27: oauth/webhook fixes, whitelabel fixes, code cleanups
icecrasher321 Aug 15, 2025
a0cf003
Merge pull request #986 from simstudioai/staging
icecrasher321 Aug 15, 2025
97b6bcc
v0.3.28: autolayout, export, copilot, kb ui improvements
waleedlatif1 Aug 16, 2025
570c07b
Merge pull request #1004 from simstudioai/staging
icecrasher321 Aug 18, 2025
af60ccd
fix: migration mem issues bypass
icecrasher321 Aug 18, 2025
3873f44
Merge pull request #1007 from simstudioai/staging
icecrasher321 Aug 18, 2025
5c56cbd
Merge pull request #1008 from simstudioai/staging
icecrasher321 Aug 18, 2025
60a9a25
Merge pull request #1009 from simstudioai/staging
icecrasher321 Aug 18, 2025
d75cc1e
v0.3.30: duplication, control bar fixes
waleedlatif1 Aug 18, 2025
1619d63
v0.3.31: webhook fixes, advanced mode parameter filtering, credential…
waleedlatif1 Aug 19, 2025
6b185be
v0.3.32: loop block max increase, url-encoded API calls, subflow logs…
waleedlatif1 Aug 20, 2025
5d74db5
v0.3.33: update copilot docs
icecrasher321 Aug 20, 2025
2c47cf4
v0.3.34: azure-openai options, billing fixes, mistral OCR via Azure, …
waleedlatif1 Aug 21, 2025
cd1bd95
fix(nextjs): downgrade nextjs due to known issue with bun commonjs mo…
waleedlatif1 Aug 21, 2025
abad362
fix(build): clear docker build cache to use correct Next.js version
waleedlatif1 Aug 21, 2025
e107363
v0.3.35: migrations, custom email address support
waleedlatif1 Aug 21, 2025
991f044
v0.3.36: workflow block logs, whitelabeling configurability, session …
waleedlatif1 Aug 22, 2025
4846f6c
v0.3.37: azure OCR api key, wand SSE, CRON helm
waleedlatif1 Aug 22, 2025
6b9567a
fix(billing): vercel cron not processing billing periods
icecrasher321 Aug 22, 2025
485ffd6
fix(billing): cleanup unused POST and fix bug with billing timing check
icecrasher321 Aug 23, 2025
e75bcac
Merge remote-tracking branch 'origin' into fix/billing-rows-filter
icecrasher321 Aug 23, 2025
b6e197d
Merge branch 'staging' into fix/billing-rows-filter
icecrasher321 Aug 23, 2025
e8c94f0
make subscriptions table source of truth for dates
icecrasher321 Aug 23, 2025
fea7fb7
update org routes
icecrasher321 Aug 23, 2025
b958fd8
make everything dependent on stripe webhook
icecrasher321 Aug 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 0 additions & 150 deletions apps/sim/app/api/billing/daily/route.ts

This file was deleted.

18 changes: 14 additions & 4 deletions apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { getUserUsageData } from '@/lib/billing/core/usage'
import { createLogger } from '@/lib/logs/console/logger'
import { db } from '@/db'
import { member, user, userStats } from '@/db/schema'
Expand Down Expand Up @@ -80,8 +81,6 @@ export async function GET(
.select({
currentPeriodCost: userStats.currentPeriodCost,
currentUsageLimit: userStats.currentUsageLimit,
billingPeriodStart: userStats.billingPeriodStart,
billingPeriodEnd: userStats.billingPeriodEnd,
usageLimitSetBy: userStats.usageLimitSetBy,
usageLimitUpdatedAt: userStats.usageLimitUpdatedAt,
lastPeriodCost: userStats.lastPeriodCost,
Expand All @@ -90,11 +89,22 @@ export async function GET(
.where(eq(userStats.userId, memberId))
.limit(1)

const computed = await getUserUsageData(memberId)

if (usageData.length > 0) {
memberData = {
...memberData,
usage: usageData[0],
} as typeof memberData & { usage: (typeof usageData)[0] }
usage: {
...usageData[0],
billingPeriodStart: computed.billingPeriodStart,
billingPeriodEnd: computed.billingPeriodEnd,
},
} as typeof memberData & {
usage: (typeof usageData)[0] & {
billingPeriodStart: Date | null
billingPeriodEnd: Date | null
}
}
}
}

Expand Down
16 changes: 13 additions & 3 deletions apps/sim/app/api/organizations/[id]/members/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getEmailSubject, renderInvitationEmail } from '@/components/emails/render-email'
import { getSession } from '@/lib/auth'
import { getUserUsageData } from '@/lib/billing/core/usage'
import { validateSeatAvailability } from '@/lib/billing/validation/seat-management'
import { sendEmail } from '@/lib/email/mailer'
import { quickValidateEmail } from '@/lib/email/validation'
Expand Down Expand Up @@ -63,7 +64,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{

// Include usage data if requested and user has admin access
if (includeUsage && hasAdminAccess) {
const membersWithUsage = await db
const base = await db
.select({
id: member.id,
userId: member.userId,
Expand All @@ -74,8 +75,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
userEmail: user.email,
currentPeriodCost: userStats.currentPeriodCost,
currentUsageLimit: userStats.currentUsageLimit,
billingPeriodStart: userStats.billingPeriodStart,
billingPeriodEnd: userStats.billingPeriodEnd,
usageLimitSetBy: userStats.usageLimitSetBy,
usageLimitUpdatedAt: userStats.usageLimitUpdatedAt,
})
Expand All @@ -84,6 +83,17 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
.leftJoin(userStats, eq(user.id, userStats.userId))
.where(eq(member.organizationId, organizationId))

const membersWithUsage = await Promise.all(
base.map(async (row) => {
const usage = await getUserUsageData(row.userId)
return {
...row,
billingPeriodStart: usage.billingPeriodStart,
billingPeriodEnd: usage.billingPeriodEnd,
}
})
)

return NextResponse.json({
success: true,
data: membersWithUsage,
Expand Down
11 changes: 3 additions & 8 deletions apps/sim/lib/billing/core/billing-periods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,9 @@ export async function initializeBillingPeriod(
end = billingPeriod.end
}

// Update user stats with billing period info
await db
.update(userStats)
.set({
billingPeriodStart: start,
billingPeriodEnd: end,
currentPeriodCost: '0',
})
.where(eq(userStats.userId, userId))
Expand Down Expand Up @@ -212,14 +209,12 @@ export async function resetUserBillingPeriod(userId: string): Promise<void> {
newPeriodEnd = billingPeriod.end
}

// Archive current period cost and reset for new period
// Archive current period cost and reset for new period (no longer updating period dates in user_stats)
await db
.update(userStats)
.set({
lastPeriodCost: currentPeriodCost, // Archive previous period
currentPeriodCost: '0', // Reset to zero for new period
billingPeriodStart: newPeriodStart,
billingPeriodEnd: newPeriodEnd,
lastPeriodCost: currentPeriodCost,
currentPeriodCost: '0',
})
.where(eq(userStats.userId, userId))

Expand Down
32 changes: 4 additions & 28 deletions apps/sim/lib/billing/core/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getUserUsageData } from '@/lib/billing/core/usage'
import { requireStripeClient } from '@/lib/billing/stripe-client'
import { createLogger } from '@/lib/logs/console/logger'
import { db } from '@/db'
import { member, organization, subscription, user, userStats } from '@/db/schema'
import { member, organization, subscription, user } from '@/db/schema'

const logger = createLogger('Billing')

Expand Down Expand Up @@ -673,45 +673,21 @@ export async function getUsersAndOrganizationsForOverageBilling(): Promise<{
continue // Skip free plans
}

// Check if subscription period ends today
// Check if subscription period ends today (range-based, inclusive of day)
let shouldBillToday = false

if (sub.periodEnd) {
const periodEnd = new Date(sub.periodEnd)
periodEnd.setUTCHours(0, 0, 0, 0) // Normalize to start of day
const endsToday = periodEnd >= today && periodEnd < tomorrow

// Bill if the subscription period ends today
if (periodEnd.getTime() === today.getTime()) {
if (endsToday) {
shouldBillToday = true
logger.info('Subscription period ends today', {
referenceId: sub.referenceId,
plan: sub.plan,
periodEnd: sub.periodEnd,
})
}
} else {
// Fallback: Check userStats billing period for users
const userStatsRecord = await db
.select({
billingPeriodEnd: userStats.billingPeriodEnd,
})
.from(userStats)
.where(eq(userStats.userId, sub.referenceId))
.limit(1)

if (userStatsRecord.length > 0 && userStatsRecord[0].billingPeriodEnd) {
const billingPeriodEnd = new Date(userStatsRecord[0].billingPeriodEnd)
billingPeriodEnd.setUTCHours(0, 0, 0, 0) // Normalize to start of day

if (billingPeriodEnd.getTime() === today.getTime()) {
shouldBillToday = true
logger.info('User billing period ends today (from userStats)', {
userId: sub.referenceId,
plan: sub.plan,
billingPeriodEnd: userStatsRecord[0].billingPeriodEnd,
})
}
}
}

if (shouldBillToday) {
Expand Down
9 changes: 3 additions & 6 deletions apps/sim/lib/billing/core/organization-billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ export async function getOrganizationBillingData(
// User stats fields
currentPeriodCost: userStats.currentPeriodCost,
currentUsageLimit: userStats.currentUsageLimit,
billingPeriodStart: userStats.billingPeriodStart,
billingPeriodEnd: userStats.billingPeriodEnd,
lastActive: userStats.lastActive,
})
.from(member)
Expand Down Expand Up @@ -151,10 +149,9 @@ export async function getOrganizationBillingData(

const averageUsagePerMember = members.length > 0 ? totalCurrentUsage / members.length : 0

// Get billing period from first member (should be consistent across org)
const firstMember = membersWithUsage[0]
const billingPeriodStart = firstMember?.billingPeriodStart || null
const billingPeriodEnd = firstMember?.billingPeriodEnd || null
// Billing period comes from the organization's subscription
const billingPeriodStart = subscription.periodStart || null
const billingPeriodEnd = subscription.periodEnd || null

return {
organizationId,
Expand Down
10 changes: 8 additions & 2 deletions apps/sim/lib/billing/core/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export async function getUserUsageData(userId: string): Promise<UsageData> {
}

const stats = userStatsData[0]
const subscription = await getHighestPrioritySubscription(userId)
const currentUsage = Number.parseFloat(
stats.currentPeriodCost?.toString() ?? stats.totalCost.toString()
)
Expand All @@ -49,14 +50,19 @@ export async function getUserUsageData(userId: string): Promise<UsageData> {
const isWarning = percentUsed >= 80
const isExceeded = currentUsage >= limit

// Derive billing period dates from subscription (source of truth).
// For free users or missing dates, expose nulls.
const billingPeriodStart = subscription?.periodStart ?? null
const billingPeriodEnd = subscription?.periodEnd ?? null

return {
currentUsage,
limit,
percentUsed,
isWarning,
isExceeded,
billingPeriodStart: stats.billingPeriodStart,
billingPeriodEnd: stats.billingPeriodEnd,
billingPeriodStart,
billingPeriodEnd,
lastPeriodCost: Number.parseFloat(stats.lastPeriodCost?.toString() || '0'),
}
} catch (error) {
Expand Down
Loading