mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(enterprise-plan): seats should be taken from metadata (#2200)
* fix(enterprise): seats need to be picked up from metadata not column * fix env var access * fix user avatar
This commit is contained in:
committed by
GitHub
parent
8e7d8c93e3
commit
d22b5783be
@@ -197,7 +197,7 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
|
||||
subscriptionData?.data?.status === 'active',
|
||||
plan: subscriptionData?.data?.plan || 'free',
|
||||
status: subscriptionData?.data?.status || 'inactive',
|
||||
seats: subscriptionData?.data?.seats || 1,
|
||||
seats: organizationBillingData?.totalSeats ?? 0,
|
||||
}
|
||||
|
||||
const usage = {
|
||||
@@ -373,7 +373,7 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
|
||||
onBadgeClick={handleBadgeClick}
|
||||
seatsText={
|
||||
permissions.canManageTeam || subscription.isEnterprise
|
||||
? `${organizationBillingData?.totalSeats || subscription.seats || 1} seats`
|
||||
? `${subscription.seats} seats`
|
||||
: undefined
|
||||
}
|
||||
current={
|
||||
|
||||
@@ -3,23 +3,20 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
|
||||
const PILL_COUNT = 8
|
||||
|
||||
type Subscription = {
|
||||
id: string
|
||||
plan: string
|
||||
status: string
|
||||
seats?: number
|
||||
referenceId: string
|
||||
cancelAtPeriodEnd?: boolean
|
||||
periodEnd?: number | Date
|
||||
trialEnd?: number | Date
|
||||
metadata?: any
|
||||
}
|
||||
|
||||
interface TeamSeatsOverviewProps {
|
||||
subscriptionData: Subscription | null
|
||||
isLoadingSubscription: boolean
|
||||
totalSeats: number
|
||||
usedSeats: number
|
||||
isLoading: boolean
|
||||
onConfirmTeamUpgrade: (seats: number) => Promise<void>
|
||||
@@ -55,6 +52,7 @@ function TeamSeatsSkeleton() {
|
||||
export function TeamSeatsOverview({
|
||||
subscriptionData,
|
||||
isLoadingSubscription,
|
||||
totalSeats,
|
||||
usedSeats,
|
||||
isLoading,
|
||||
onConfirmTeamUpgrade,
|
||||
@@ -78,7 +76,7 @@ export function TeamSeatsOverview({
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
onConfirmTeamUpgrade(2) // Start with 2 seats as default
|
||||
onConfirmTeamUpgrade(2)
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
@@ -89,7 +87,6 @@ export function TeamSeatsOverview({
|
||||
)
|
||||
}
|
||||
|
||||
const totalSeats = subscriptionData.seats || 0
|
||||
const isEnterprise = checkEnterprisePlan(subscriptionData)
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Tooltip,
|
||||
} from '@/components/emcn'
|
||||
import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
|
||||
interface TeamSeatsProps {
|
||||
open: boolean
|
||||
@@ -52,7 +51,7 @@ export function TeamSeats({
|
||||
}
|
||||
}, [open, initialSeats])
|
||||
|
||||
const costPerSeat = env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT
|
||||
const costPerSeat = DEFAULT_TEAM_TIER_COST_LIMIT
|
||||
const totalMonthlyCost = selectedSeats * costPerSeat
|
||||
const costChange = currentSeats ? (selectedSeats - currentSeats) * costPerSeat : 0
|
||||
|
||||
|
||||
@@ -63,10 +63,10 @@ export function TeamUsage({ hasAdminAccess }: TeamUsageProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
const currentUsage = billingData.totalCurrentUsage || 0
|
||||
const currentCap = billingData.totalUsageLimit || billingData.minimumBillingAmount || 0
|
||||
const minimumBilling = billingData.minimumBillingAmount || 0
|
||||
const seatsCount = billingData.seatsCount || 1
|
||||
const currentUsage = billingData.totalCurrentUsage ?? 0
|
||||
const currentCap = billingData.totalUsageLimit ?? billingData.minimumBillingAmount ?? 0
|
||||
const minimumBilling = billingData.minimumBillingAmount ?? 0
|
||||
const seatsCount = billingData.seatsCount ?? 0
|
||||
const percentUsed =
|
||||
currentCap > 0 ? Math.round(Math.min((currentUsage / currentCap) * 100, 100)) : 0
|
||||
const status: 'ok' | 'warning' | 'exceeded' =
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Skeleton } from '@/components/ui'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
|
||||
import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import {
|
||||
generateSlug,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
useCreateOrganization,
|
||||
useInviteMember,
|
||||
useOrganization,
|
||||
useOrganizationBilling,
|
||||
useOrganizationSubscription,
|
||||
useOrganizations,
|
||||
useRemoveMember,
|
||||
@@ -56,6 +56,8 @@ export function TeamManagement() {
|
||||
error: subscriptionError,
|
||||
} = useOrganizationSubscription(activeOrganization?.id || '')
|
||||
|
||||
const { data: organizationBillingData } = useOrganizationBilling(activeOrganization?.id || '')
|
||||
|
||||
const inviteMutation = useInviteMember()
|
||||
const removeMemberMutation = useRemoveMember()
|
||||
const updateSeatsMutation = useUpdateSeats()
|
||||
@@ -89,6 +91,7 @@ export function TeamManagement() {
|
||||
const userRole = getUserRole(organization, session?.user?.email)
|
||||
const adminOrOwner = isAdminOrOwner(organization, session?.user?.email)
|
||||
const usedSeats = getUsedSeats(organization)
|
||||
const totalSeats = organizationBillingData?.data?.totalSeats ?? 0
|
||||
|
||||
useEffect(() => {
|
||||
if ((hasTeamPlan || hasEnterprisePlan) && session?.user?.name && !orgName) {
|
||||
@@ -238,11 +241,11 @@ export function TeamManagement() {
|
||||
}, [session?.user?.id, activeOrganization?.id, subscriptionData, usedSeats, updateSeatsMutation])
|
||||
|
||||
const handleAddSeatDialog = useCallback(() => {
|
||||
if (subscriptionData) {
|
||||
setNewSeatCount((subscriptionData.seats || 1) + 1)
|
||||
if (subscriptionData && !checkEnterprisePlan(subscriptionData)) {
|
||||
setNewSeatCount(totalSeats + 1)
|
||||
setIsAddSeatDialogOpen(true)
|
||||
}
|
||||
}, [subscriptionData?.seats])
|
||||
}, [subscriptionData, totalSeats])
|
||||
|
||||
const confirmAddSeats = useCallback(
|
||||
async (selectedSeats?: number) => {
|
||||
@@ -370,6 +373,7 @@ export function TeamManagement() {
|
||||
<TeamSeatsOverview
|
||||
subscriptionData={subscriptionData || null}
|
||||
isLoadingSubscription={isLoadingSubscription}
|
||||
totalSeats={totalSeats}
|
||||
usedSeats={usedSeats.used}
|
||||
isLoading={isLoading}
|
||||
onConfirmTeamUpgrade={confirmTeamUpgrade}
|
||||
@@ -394,8 +398,8 @@ export function TeamManagement() {
|
||||
onLoadUserWorkspaces={async () => {}} // No-op: data is auto-loaded by React Query
|
||||
onWorkspaceToggle={handleWorkspaceToggle}
|
||||
inviteSuccess={inviteSuccess}
|
||||
availableSeats={Math.max(0, (subscriptionData?.seats || 0) - usedSeats.used)}
|
||||
maxSeats={subscriptionData?.seats || 0}
|
||||
availableSeats={Math.max(0, totalSeats - usedSeats.used)}
|
||||
maxSeats={totalSeats}
|
||||
invitationError={inviteMutation.error}
|
||||
isLoadingWorkspaces={isLoadingWorkspaces}
|
||||
/>
|
||||
@@ -481,9 +485,8 @@ export function TeamManagement() {
|
||||
<ul className='ml-4 list-disc space-y-[8px] text-[var(--text-muted)] text-xs'>
|
||||
<li>
|
||||
Your team is billed a minimum of $
|
||||
{(subscriptionData?.seats || 0) *
|
||||
(env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT)}
|
||||
/month for {subscriptionData?.seats || 0} licensed seats
|
||||
{(subscriptionData?.seats ?? 0) * DEFAULT_TEAM_TIER_COST_LIMIT}
|
||||
/month for {subscriptionData?.seats ?? 0} licensed seats
|
||||
</li>
|
||||
<li>All team member usage is pooled together from a shared limit</li>
|
||||
<li>
|
||||
@@ -528,23 +531,25 @@ export function TeamManagement() {
|
||||
}
|
||||
/>
|
||||
|
||||
<TeamSeats
|
||||
open={isAddSeatDialogOpen}
|
||||
onOpenChange={setIsAddSeatDialogOpen}
|
||||
title='Add Team Seats'
|
||||
description={`Each seat costs $${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT}/month and provides $${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT} in monthly inference credits. Adjust the number of licensed seats for your team.`}
|
||||
currentSeats={subscriptionData?.seats || 1}
|
||||
initialSeats={newSeatCount}
|
||||
isLoading={isUpdatingSeats}
|
||||
error={updateSeatsMutation.error}
|
||||
onConfirm={async (selectedSeats: number) => {
|
||||
setNewSeatCount(selectedSeats)
|
||||
await confirmAddSeats(selectedSeats)
|
||||
}}
|
||||
confirmButtonText='Update Seats'
|
||||
showCostBreakdown={true}
|
||||
isCancelledAtPeriodEnd={subscriptionData?.cancelAtPeriodEnd}
|
||||
/>
|
||||
{subscriptionData && !checkEnterprisePlan(subscriptionData) && (
|
||||
<TeamSeats
|
||||
open={isAddSeatDialogOpen}
|
||||
onOpenChange={setIsAddSeatDialogOpen}
|
||||
title='Add Team Seats'
|
||||
description={`Each seat costs $${DEFAULT_TEAM_TIER_COST_LIMIT}/month and provides $${DEFAULT_TEAM_TIER_COST_LIMIT} in monthly inference credits. Adjust the number of licensed seats for your team.`}
|
||||
currentSeats={totalSeats}
|
||||
initialSeats={newSeatCount}
|
||||
isLoading={isUpdatingSeats}
|
||||
error={updateSeatsMutation.error}
|
||||
onConfirm={async (selectedSeats: number) => {
|
||||
setNewSeatCount(selectedSeats)
|
||||
await confirmAddSeats(selectedSeats)
|
||||
}}
|
||||
confirmButtonText='Update Seats'
|
||||
showCostBreakdown={true}
|
||||
isCancelledAtPeriodEnd={subscriptionData?.cancelAtPeriodEnd}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export function UserAvatar({
|
||||
sizes={`${size}px`}
|
||||
className='object-cover'
|
||||
referrerPolicy='no-referrer'
|
||||
unoptimized={avatarUrl.startsWith('http')}
|
||||
unoptimized
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -115,7 +115,7 @@ export async function checkUsageStatus(userId: string): Promise<UsageData> {
|
||||
const orgSub = await getOrganizationSubscription(org.id)
|
||||
if (orgSub?.seats) {
|
||||
const { basePrice } = getPlanPricing(orgSub.plan)
|
||||
orgCap = (orgSub.seats || 1) * basePrice
|
||||
orgCap = (orgSub.seats ?? 0) * basePrice
|
||||
} else {
|
||||
// If no subscription, use team default
|
||||
const { basePrice } = getPlanPricing('team')
|
||||
|
||||
@@ -96,9 +96,6 @@ export function isAtLeastTeam(subscriptionData: SubscriptionData | null | undefi
|
||||
return status.isTeam || status.isEnterprise
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can upgrade
|
||||
*/
|
||||
export function canUpgrade(subscriptionData: SubscriptionData | null | undefined): boolean {
|
||||
const status = getSubscriptionStatus(subscriptionData)
|
||||
return status.plan === 'free' || status.plan === 'pro'
|
||||
|
||||
@@ -144,7 +144,7 @@ export async function calculateSubscriptionOverage(sub: {
|
||||
|
||||
const totalUsageWithDeparted = totalTeamUsage + departedUsage
|
||||
const { basePrice } = getPlanPricing(sub.plan)
|
||||
const baseSubscriptionAmount = (sub.seats || 1) * basePrice
|
||||
const baseSubscriptionAmount = (sub.seats ?? 0) * basePrice
|
||||
totalOverage = Math.max(0, totalUsageWithDeparted - baseSubscriptionAmount)
|
||||
|
||||
logger.info('Calculated team overage', {
|
||||
@@ -286,7 +286,7 @@ export async function getSimplifiedBillingSummary(
|
||||
|
||||
const { basePrice: basePricePerSeat } = getPlanPricing(subscription.plan)
|
||||
// Use licensed seats from Stripe as source of truth
|
||||
const licensedSeats = subscription.seats || 1
|
||||
const licensedSeats = subscription.seats ?? 0
|
||||
const totalBasePrice = basePricePerSeat * licensedSeats // Based on Stripe subscription
|
||||
|
||||
let totalCurrentUsage = 0
|
||||
|
||||
@@ -2,7 +2,7 @@ import { db } from '@sim/db'
|
||||
import { member, organization, subscription, user, userStats } from '@sim/db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { getPlanPricing } from '@/lib/billing/core/billing'
|
||||
import { getFreeTierLimit } from '@/lib/billing/subscriptions/utils'
|
||||
import { getEffectiveSeats, getFreeTierLimit } from '@/lib/billing/subscriptions/utils'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('OrganizationBilling')
|
||||
@@ -133,9 +133,12 @@ export async function getOrganizationBillingData(
|
||||
// Get per-seat pricing for the plan
|
||||
const { basePrice: pricePerSeat } = getPlanPricing(subscription.plan)
|
||||
|
||||
// Use Stripe subscription seats as source of truth
|
||||
// Ensure we always have at least 1 seat (protect against 0 or falsy values)
|
||||
const licensedSeats = Math.max(subscription.seats || 1, 1)
|
||||
const licensedSeats = subscription.seats ?? 0
|
||||
|
||||
// For seat count used in UI (invitations, team management):
|
||||
// Team: seats column (Stripe quantity)
|
||||
// Enterprise: metadata.seats (allocated seats, not Stripe quantity which is always 1)
|
||||
const effectiveSeats = getEffectiveSeats(subscription)
|
||||
|
||||
// Calculate minimum billing amount
|
||||
let minimumBillingAmount: number
|
||||
@@ -174,9 +177,9 @@ export async function getOrganizationBillingData(
|
||||
organizationName: organizationData.name || '',
|
||||
subscriptionPlan: subscription.plan,
|
||||
subscriptionStatus: subscription.status || 'inactive',
|
||||
totalSeats: Math.max(subscription.seats || 1, 1),
|
||||
totalSeats: effectiveSeats, // Uses metadata.seats for enterprise, seats column for team
|
||||
usedSeats: members.length,
|
||||
seatsCount: licensedSeats,
|
||||
seatsCount: licensedSeats, // Used for billing calculations (Stripe quantity)
|
||||
totalCurrentUsage: roundCurrency(totalCurrentUsage),
|
||||
totalUsageLimit: roundCurrency(totalUsageLimit),
|
||||
minimumBillingAmount: roundCurrency(minimumBillingAmount),
|
||||
@@ -232,9 +235,8 @@ export async function updateOrganizationUsageLimit(
|
||||
}
|
||||
}
|
||||
|
||||
// Team plans have minimum based on seats
|
||||
const { basePrice } = getPlanPricing(subscription.plan)
|
||||
const minimumLimit = Math.max(subscription.seats || 1, 1) * basePrice
|
||||
const minimumLimit = (subscription.seats ?? 0) * basePrice
|
||||
|
||||
// Validate new limit is not below minimum
|
||||
if (newLimit < minimumLimit) {
|
||||
|
||||
@@ -95,7 +95,7 @@ export async function getUserUsageData(userId: string): Promise<UsageData> {
|
||||
|
||||
const { getPlanPricing } = await import('@/lib/billing/core/billing')
|
||||
const { basePrice } = getPlanPricing(subscription.plan)
|
||||
const minimum = (subscription.seats || 1) * basePrice
|
||||
const minimum = (subscription.seats ?? 0) * basePrice
|
||||
|
||||
if (orgData.length > 0 && orgData[0].orgUsageLimit) {
|
||||
const configured = Number.parseFloat(orgData[0].orgUsageLimit)
|
||||
@@ -168,7 +168,7 @@ export async function getUserUsageLimitInfo(userId: string): Promise<UsageLimitI
|
||||
|
||||
const { getPlanPricing } = await import('@/lib/billing/core/billing')
|
||||
const { basePrice } = getPlanPricing(subscription.plan)
|
||||
const minimum = (subscription.seats || 1) * basePrice
|
||||
const minimum = (subscription.seats ?? 0) * basePrice
|
||||
|
||||
if (orgData.length > 0 && orgData[0].orgUsageLimit) {
|
||||
const configured = Number.parseFloat(orgData[0].orgUsageLimit)
|
||||
@@ -361,14 +361,14 @@ export async function getUserUsageLimit(userId: string): Promise<number> {
|
||||
const configured = Number.parseFloat(orgData[0].orgUsageLimit)
|
||||
const { getPlanPricing } = await import('@/lib/billing/core/billing')
|
||||
const { basePrice } = getPlanPricing(subscription.plan)
|
||||
const minimum = (subscription.seats || 1) * basePrice
|
||||
const minimum = (subscription.seats ?? 0) * basePrice
|
||||
return Math.max(configured, minimum)
|
||||
}
|
||||
|
||||
// If org hasn't set a custom limit, use minimum (seats × cost per seat)
|
||||
const { getPlanPricing } = await import('@/lib/billing/core/billing')
|
||||
const { basePrice } = getPlanPricing(subscription.plan)
|
||||
return (subscription.seats || 1) * basePrice
|
||||
return (subscription.seats ?? 0) * basePrice
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DEFAULT_PRO_TIER_COST_LIMIT,
|
||||
DEFAULT_TEAM_TIER_COST_LIMIT,
|
||||
} from '@/lib/billing/constants'
|
||||
import type { EnterpriseSubscriptionMetadata } from '@/lib/billing/types'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
|
||||
/**
|
||||
@@ -38,6 +39,38 @@ export function checkEnterprisePlan(subscription: any): boolean {
|
||||
return subscription?.plan === 'enterprise' && subscription?.status === 'active'
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if metadata is valid EnterpriseSubscriptionMetadata
|
||||
*/
|
||||
function isEnterpriseMetadata(metadata: unknown): metadata is EnterpriseSubscriptionMetadata {
|
||||
return (
|
||||
!!metadata &&
|
||||
typeof metadata === 'object' &&
|
||||
'seats' in metadata &&
|
||||
typeof (metadata as EnterpriseSubscriptionMetadata).seats === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
export function getEffectiveSeats(subscription: any): number {
|
||||
if (!subscription) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (subscription.plan === 'enterprise') {
|
||||
const metadata = subscription.metadata as EnterpriseSubscriptionMetadata | null
|
||||
if (isEnterpriseMetadata(metadata)) {
|
||||
return Number.parseInt(metadata.seats, 10)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if (subscription.plan === 'team') {
|
||||
return subscription.seats ?? 0
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
export function checkProPlan(subscription: any): boolean {
|
||||
return subscription?.plan === 'pro' && subscription?.status === 'active'
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ export async function checkAndBillOrganizationOverageThreshold(
|
||||
}
|
||||
|
||||
const { basePrice: basePricePerSeat } = getPlanPricing(orgSubscription.plan)
|
||||
const basePrice = basePricePerSeat * (orgSubscription.seats || 1)
|
||||
const basePrice = basePricePerSeat * (orgSubscription.seats ?? 0)
|
||||
const currentOverage = Math.max(0, totalTeamUsage - basePrice)
|
||||
const unbilledOverage = Math.max(0, currentOverage - totalBilledOverage)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { db } from '@sim/db'
|
||||
import { invitation, member, organization, subscription, user, userStats } from '@sim/db/schema'
|
||||
import { and, count, eq } from 'drizzle-orm'
|
||||
import { getOrganizationSubscription } from '@/lib/billing/core/billing'
|
||||
import { getEffectiveSeats } from '@/lib/billing/subscriptions/utils'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { quickValidateEmail } from '@/lib/messaging/email/validation'
|
||||
|
||||
@@ -66,9 +67,9 @@ export async function validateSeatAvailability(
|
||||
const currentSeats = memberCount[0]?.count || 0
|
||||
|
||||
// Determine seat limits based on subscription
|
||||
// Team: seats from Stripe subscription quantity
|
||||
// Enterprise: seats from metadata (stored in subscription.seats)
|
||||
const maxSeats = subscription.seats || 1
|
||||
// Team: seats from Stripe subscription quantity (seats column)
|
||||
// Enterprise: seats from metadata.seats (not from seats column which is always 1)
|
||||
const maxSeats = getEffectiveSeats(subscription)
|
||||
|
||||
const availableSeats = Math.max(0, maxSeats - currentSeats)
|
||||
const canInvite = availableSeats >= additionalSeats
|
||||
@@ -140,7 +141,8 @@ export async function getOrganizationSeatInfo(
|
||||
|
||||
const currentSeats = memberCount[0]?.count || 0
|
||||
|
||||
const maxSeats = subscription.seats || 1
|
||||
// Team: seats from column, Enterprise: seats from metadata
|
||||
const maxSeats = getEffectiveSeats(subscription)
|
||||
|
||||
const canAddSeats = subscription.plan !== 'enterprise'
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ export async function handleManualEnterpriseSubscription(event: Stripe.Event) {
|
||||
? new Date(referenceItem.current_period_end * 1000)
|
||||
: null,
|
||||
cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end ?? null,
|
||||
seats,
|
||||
seats: 1, // Enterprise uses metadata.seats for actual seat count, column is always 1
|
||||
trialStart: stripeSubscription.trial_start
|
||||
? new Date(stripeSubscription.trial_start * 1000)
|
||||
: null,
|
||||
@@ -140,7 +140,7 @@ export async function handleManualEnterpriseSubscription(event: Stripe.Event) {
|
||||
periodStart: subscriptionRow.periodStart,
|
||||
periodEnd: subscriptionRow.periodEnd,
|
||||
cancelAtPeriodEnd: subscriptionRow.cancelAtPeriodEnd,
|
||||
seats: subscriptionRow.seats,
|
||||
seats: 1, // Enterprise uses metadata.seats for actual seat count, column is always 1
|
||||
trialStart: subscriptionRow.trialStart,
|
||||
trialEnd: subscriptionRow.trialEnd,
|
||||
metadata: subscriptionRow.metadata,
|
||||
|
||||
Reference in New Issue
Block a user