Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
219a065
v0.4.12: guardrails, mistral models, privacy policy updates (#1608)
waleedlatif1 Oct 12, 2025
7f82ed3
v0.4.13: bugfixes for dev containers, posthog redirect, helm updates
icecrasher321 Oct 13, 2025
fb0fa1f
v0.4.14: canvas speedup and copilot context window
Sg312 Oct 14, 2025
2bc8c7b
v0.4.15: helm chart updates, telegram tools, youtube tools, file uplo…
waleedlatif1 Oct 15, 2025
04f109c
v0.4.16: executions dashboard, UI fixes, zep tools, slack fixes
icecrasher321 Oct 16, 2025
da091df
v0.4.17: input format + files support for webhooks, docs updates, das…
waleedlatif1 Oct 16, 2025
e4ddeb0
v0.4.18: file upload tools, copilot upgrade, docs changes, model filt…
icecrasher321 Oct 19, 2025
641e353
v0.4.19: landing page fix
icecrasher321 Oct 19, 2025
9751c9f
v0.4.20: internal request, kb url fixes, docs styling
icecrasher321 Oct 21, 2025
1b7437a
v0.4.21: more internal auth changes, supabase vector search tool
icecrasher321 Oct 22, 2025
71ae27b
v0.4.22: fix execution context pass for google sheets
icecrasher321 Oct 22, 2025
9b2490c
v0.4.23: webflow tools + triggers, copilot api key fix (#1723)
waleedlatif1 Oct 23, 2025
7f1ff7f
fix(billing): should allow restoring subscription (#1728)
icecrasher321 Oct 25, 2025
a02016e
v0.4.24: sso for chat deployment, usage indicator for file storage, m…
icecrasher321 Oct 27, 2025
9a4b9e2
v0.4.25: variables block, sort ordering for kb, careers page, storage…
waleedlatif1 Oct 29, 2025
edf4dda
Rename devcontainer.json to devcontainer.json.
Dustinturner44 Nov 10, 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
File renamed without changes.
7 changes: 5 additions & 2 deletions apps/sim/app/api/billing/portal/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { db } from '@sim/db'
import { subscription as subscriptionTable, user } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { and, eq, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { requireStripeClient } from '@/lib/billing/stripe-client'
Expand Down Expand Up @@ -38,7 +38,10 @@ export async function POST(request: NextRequest) {
.where(
and(
eq(subscriptionTable.referenceId, organizationId),
eq(subscriptionTable.status, 'active')
or(
eq(subscriptionTable.status, 'active'),
eq(subscriptionTable.cancelAtPeriodEnd, true)
)
)
)
.limit(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { useSession, useSubscription } from '@/lib/auth-client'
import { createLogger } from '@/lib/logs/console/logger'
import { getBaseUrl } from '@/lib/urls/utils'
Expand All @@ -30,6 +29,7 @@ interface CancelSubscriptionProps {
}
subscriptionData?: {
periodEnd?: Date | null
cancelAtPeriodEnd?: boolean
}
}

Expand Down Expand Up @@ -127,35 +127,48 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
const subscriptionStatus = getSubscriptionStatus()
const activeOrgId = activeOrganization?.id

// For team/enterprise plans, get the subscription ID from organization store
if ((subscriptionStatus.isTeam || subscriptionStatus.isEnterprise) && activeOrgId) {
const orgSubscription = useOrganizationStore.getState().subscriptionData
if (isCancelAtPeriodEnd) {
if (!betterAuthSubscription.restore) {
throw new Error('Subscription restore not available')
}

let referenceId: string
let subscriptionId: string | undefined

if ((subscriptionStatus.isTeam || subscriptionStatus.isEnterprise) && activeOrgId) {
const orgSubscription = useOrganizationStore.getState().subscriptionData
referenceId = activeOrgId
subscriptionId = orgSubscription?.id
} else {
// For personal subscriptions, use user ID and let better-auth find the subscription
referenceId = session.user.id
subscriptionId = undefined
}

logger.info('Restoring subscription', { referenceId, subscriptionId })

if (orgSubscription?.id && orgSubscription?.cancelAtPeriodEnd) {
// Restore the organization subscription
if (!betterAuthSubscription.restore) {
throw new Error('Subscription restore not available')
}

const result = await betterAuthSubscription.restore({
referenceId: activeOrgId,
subscriptionId: orgSubscription.id,
})
logger.info('Organization subscription restored successfully', result)
// Build restore params - only include subscriptionId if we have one (team/enterprise)
const restoreParams: any = { referenceId }
if (subscriptionId) {
restoreParams.subscriptionId = subscriptionId
}

const result = await betterAuthSubscription.restore(restoreParams)

logger.info('Subscription restored successfully', result)
}

// Refresh state and close
await refresh()
if (activeOrgId) {
await loadOrganizationSubscription(activeOrgId)
await refreshOrganization().catch(() => {})
}

setIsDialogOpen(false)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to keep subscription'
const errorMessage = error instanceof Error ? error.message : 'Failed to restore subscription'
setError(errorMessage)
logger.error('Failed to keep subscription', { error })
logger.error('Failed to restore subscription', { error })
} finally {
setIsLoading(false)
}
Expand Down Expand Up @@ -190,19 +203,15 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
const periodEndDate = getPeriodEndDate()

// Check if subscription is set to cancel at period end
const isCancelAtPeriodEnd = (() => {
const subscriptionStatus = getSubscriptionStatus()
if (subscriptionStatus.isTeam || subscriptionStatus.isEnterprise) {
return useOrganizationStore.getState().subscriptionData?.cancelAtPeriodEnd === true
}
return false
})()
const isCancelAtPeriodEnd = subscriptionData?.cancelAtPeriodEnd === true

return (
<>
<div className='flex items-center justify-between'>
<div>
<span className='font-medium text-sm'>Manage Subscription</span>
<span className='font-medium text-sm'>
{isCancelAtPeriodEnd ? 'Restore Subscription' : 'Manage Subscription'}
</span>
{isCancelAtPeriodEnd && (
<p className='mt-1 text-muted-foreground text-xs'>
You'll keep access until {formatDate(periodEndDate)}
Expand All @@ -217,22 +226,24 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
'h-8 rounded-[8px] font-medium text-xs transition-all duration-200',
error
? 'border-red-500 text-red-500 dark:border-red-500 dark:text-red-500'
: 'text-muted-foreground hover:border-red-500 hover:bg-red-500 hover:text-white dark:hover:border-red-500 dark:hover:bg-red-500'
: isCancelAtPeriodEnd
? 'text-muted-foreground hover:border-green-500 hover:bg-green-500 hover:text-white dark:hover:border-green-500 dark:hover:bg-green-500'
: 'text-muted-foreground hover:border-red-500 hover:bg-red-500 hover:text-white dark:hover:border-red-500 dark:hover:bg-red-500'
)}
>
{error ? 'Error' : 'Manage'}
{error ? 'Error' : isCancelAtPeriodEnd ? 'Restore' : 'Manage'}
</Button>
</div>

<AlertDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{isCancelAtPeriodEnd ? 'Manage' : 'Cancel'} {subscription.plan} subscription?
{isCancelAtPeriodEnd ? 'Restore' : 'Cancel'} {subscription.plan} subscription?
</AlertDialogTitle>
<AlertDialogDescription>
{isCancelAtPeriodEnd
? 'Your subscription is set to cancel at the end of the billing period. You can reactivate it or manage other settings.'
? 'Your subscription is set to cancel at the end of the billing period. Would you like to keep your subscription active?'
: `You'll be redirected to Stripe to manage your subscription. You'll keep access until ${formatDate(
periodEndDate
)}, then downgrade to free plan.`}{' '}
Expand Down Expand Up @@ -260,38 +271,23 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
<AlertDialogFooter className='flex'>
<AlertDialogCancel
className='h-9 w-full rounded-[8px]'
onClick={handleKeep}
onClick={isCancelAtPeriodEnd ? () => setIsDialogOpen(false) : handleKeep}
disabled={isLoading}
>
Keep Subscription
{isCancelAtPeriodEnd ? 'Cancel' : 'Keep Subscription'}
</AlertDialogCancel>

{(() => {
const subscriptionStatus = getSubscriptionStatus()
if (
subscriptionStatus.isPaid &&
(activeOrganization?.id
? useOrganizationStore.getState().subscriptionData?.cancelAtPeriodEnd
: false)
) {
if (subscriptionStatus.isPaid && isCancelAtPeriodEnd) {
return (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<div className='w-full'>
<AlertDialogAction
disabled
className='h-9 w-full cursor-not-allowed rounded-[8px] bg-muted text-muted-foreground opacity-50'
>
Continue
</AlertDialogAction>
</div>
</TooltipTrigger>
<TooltipContent side='top'>
<p>Subscription will be cancelled at end of billing period</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<AlertDialogAction
onClick={handleKeep}
className='h-9 w-full rounded-[8px] bg-green-500 text-white transition-all duration-200 hover:bg-green-600 dark:bg-green-500 dark:hover:bg-green-600'
disabled={isLoading}
>
{isLoading ? 'Restoring...' : 'Restore Subscription'}
</AlertDialogAction>
)
}
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
}}
subscriptionData={{
periodEnd: subscriptionData?.periodEnd || null,
cancelAtPeriodEnd: subscriptionData?.cancelAtPeriodEnd,
}}
/>
</div>
Expand Down
12 changes: 12 additions & 0 deletions apps/sim/lib/billing/core/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export async function getSimplifiedBillingSummary(
metadata: any
stripeSubscriptionId: string | null
periodEnd: Date | string | null
cancelAtPeriodEnd?: boolean
// Usage details
usage: {
current: number
Expand Down Expand Up @@ -318,6 +319,7 @@ export async function getSimplifiedBillingSummary(
metadata: subscription.metadata || null,
stripeSubscriptionId: subscription.stripeSubscriptionId || null,
periodEnd: subscription.periodEnd || null,
cancelAtPeriodEnd: subscription.cancelAtPeriodEnd || undefined,
// Usage details
usage: {
current: usageData.currentUsage,
Expand Down Expand Up @@ -393,6 +395,7 @@ export async function getSimplifiedBillingSummary(
metadata: subscription?.metadata || null,
stripeSubscriptionId: subscription?.stripeSubscriptionId || null,
periodEnd: subscription?.periodEnd || null,
cancelAtPeriodEnd: subscription?.cancelAtPeriodEnd || undefined,
// Usage details
usage: {
current: currentUsage,
Expand Down Expand Up @@ -450,5 +453,14 @@ function getDefaultBillingSummary(type: 'individual' | 'organization') {
lastPeriodCost: 0,
daysRemaining: 0,
},
...(type === 'organization' && {
organizationData: {
seatCount: 0,
memberCount: 0,
totalBasePrice: 0,
totalCurrentUsage: 0,
totalOverage: 0,
},
}),
}
}
1 change: 1 addition & 0 deletions apps/sim/stores/subscription/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface SubscriptionData {
metadata: any | null
stripeSubscriptionId: string | null
periodEnd: Date | null
cancelAtPeriodEnd?: boolean
usage: UsageData
billingBlocked?: boolean
}
Expand Down