2
0

📄 Add Commercial License for ee folder (#1532)

This commit is contained in:
Baptiste Arnaud
2024-05-23 10:42:23 +02:00
committed by GitHub
parent 5680829906
commit 0eacbebbbe
246 changed files with 1472 additions and 1588 deletions

View File

@@ -0,0 +1,104 @@
import { isAdminWriteWorkspaceForbidden } from '@typebot.io/db-rules/isAdminWriteWorkspaceForbidden'
import { env } from '@typebot.io/env'
import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
import Stripe from 'stripe'
import { createCheckoutSessionUrl } from '../helpers/createCheckoutSessionUrl'
import { TRPCError } from '@trpc/server'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
returnUrl: string
email: string
company: string
plan: 'STARTER' | 'PRO'
currency: 'usd' | 'eur'
vat?: {
type: string
value: string
}
}
export const createCheckoutSession = async ({
workspaceId,
user,
returnUrl,
email,
company,
plan,
currency,
vat,
}: Props) => {
if (!env.STRIPE_SECRET_KEY)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Stripe environment variables are missing',
})
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
stripeId: true,
members: {
select: {
userId: true,
role: true,
},
},
},
})
if (!workspace || isAdminWriteWorkspaceForbidden(workspace, user))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
if (workspace.stripeId)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Customer already exists, use updateSubscription endpoint.',
})
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
await prisma.user.updateMany({
where: {
id: user.id,
},
data: {
company,
},
})
const customer = await stripe.customers.create({
email,
name: company,
metadata: { workspaceId },
tax_id_data: vat
? [vat as Stripe.CustomerCreateParams.TaxIdDatum]
: undefined,
})
const checkoutUrl = await createCheckoutSessionUrl(stripe)({
customerId: customer.id,
userId: user.id,
workspaceId,
currency,
plan,
returnUrl,
})
if (!checkoutUrl)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Stripe checkout session creation failed',
})
return {
checkoutUrl,
}
}

View File

@@ -0,0 +1,158 @@
import { TRPCError } from '@trpc/server'
import { isAdminWriteWorkspaceForbidden } from '@typebot.io/db-rules/isAdminWriteWorkspaceForbidden'
import { env } from '@typebot.io/env'
import prisma from '@typebot.io/lib/prisma'
import { Plan, User } from '@typebot.io/prisma'
import Stripe from 'stripe'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
returnUrl: string
email: string
}
export const createCustomCheckoutSession = async ({
workspaceId,
user,
returnUrl,
email,
}: Props) => {
if (!env.STRIPE_SECRET_KEY)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Stripe environment variables are missing',
})
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
stripeId: true,
claimableCustomPlan: true,
name: true,
members: {
select: {
userId: true,
role: true,
},
},
},
})
if (
!workspace?.claimableCustomPlan ||
workspace.claimableCustomPlan.claimedAt ||
isAdminWriteWorkspaceForbidden(workspace, user)
)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Custom plan not found',
})
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const vat =
workspace.claimableCustomPlan.vatValue &&
workspace.claimableCustomPlan.vatType
? ({
type: workspace.claimableCustomPlan.vatType,
value: workspace.claimableCustomPlan.vatValue,
} as Stripe.CustomerCreateParams.TaxIdDatum)
: undefined
const customer = workspace.stripeId
? await stripe.customers.retrieve(workspace.stripeId)
: await stripe.customers.create({
email,
name: workspace.claimableCustomPlan.companyName ?? workspace.name,
metadata: { workspaceId },
tax_id_data: vat ? [vat] : undefined,
})
const session = await stripe.checkout.sessions.create({
success_url: `${returnUrl}?stripe=${Plan.CUSTOM}&success=true`,
cancel_url: `${returnUrl}?stripe=cancel`,
allow_promotion_codes: true,
customer: customer.id,
customer_update: {
address: 'auto',
name: 'never',
},
mode: 'subscription',
metadata: {
claimableCustomPlanId: workspace.claimableCustomPlan.id,
},
currency: workspace.claimableCustomPlan.currency,
billing_address_collection: 'required',
automatic_tax: { enabled: true },
line_items: [
{
price_data: {
currency: workspace.claimableCustomPlan.currency,
tax_behavior: 'exclusive',
recurring: {
interval: workspace.claimableCustomPlan.isYearly ? 'year' : 'month',
},
product_data: {
name: workspace.claimableCustomPlan.name,
description: workspace.claimableCustomPlan.description ?? undefined,
},
unit_amount: workspace.claimableCustomPlan.price * 100,
},
quantity: 1,
},
{
price_data: {
currency: workspace.claimableCustomPlan.currency,
tax_behavior: 'exclusive',
recurring: {
interval: workspace.claimableCustomPlan.isYearly ? 'year' : 'month',
},
product_data: {
name: 'Included chats per month',
},
unit_amount: 0,
},
quantity: workspace.claimableCustomPlan.chatsLimit,
},
{
price_data: {
currency: workspace.claimableCustomPlan.currency,
tax_behavior: 'exclusive',
recurring: {
interval: workspace.claimableCustomPlan.isYearly ? 'year' : 'month',
},
product_data: {
name: 'Included storage per month',
},
unit_amount: 0,
},
quantity: workspace.claimableCustomPlan.storageLimit,
},
{
price_data: {
currency: workspace.claimableCustomPlan.currency,
tax_behavior: 'exclusive',
recurring: {
interval: workspace.claimableCustomPlan.isYearly ? 'year' : 'month',
},
product_data: {
name: 'Included seats',
},
unit_amount: 0,
},
quantity: workspace.claimableCustomPlan.seatsLimit,
},
],
})
if (!session.url)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Stripe checkout session creation failed',
})
return {
checkoutUrl: session.url,
}
}

View File

@@ -0,0 +1,47 @@
import { TRPCError } from '@trpc/server'
import { isAdminWriteWorkspaceForbidden } from '@typebot.io/db-rules/isAdminWriteWorkspaceForbidden'
import { env } from '@typebot.io/env'
import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
import Stripe from 'stripe'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
}
export const getBillingPortalUrl = async ({ workspaceId, user }: Props) => {
if (!env.STRIPE_SECRET_KEY)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'STRIPE_SECRET_KEY var is missing',
})
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
stripeId: true,
members: {
select: {
userId: true,
role: true,
},
},
},
})
if (!workspace?.stripeId || isAdminWriteWorkspaceForbidden(workspace, user))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const portalSession = await stripe.billingPortal.sessions.create({
customer: workspace.stripeId,
return_url: `${env.NEXTAUTH_URL}/typebots`,
})
return {
billingPortalUrl: portalSession.url,
}
}

View File

@@ -0,0 +1,74 @@
import { TRPCError } from '@trpc/server'
import { isReadWorkspaceFobidden } from '@typebot.io/db-rules/isReadWorkspaceFobidden'
import { env } from '@typebot.io/env'
import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
import Stripe from 'stripe'
import { subscriptionSchema } from '@typebot.io/schemas/features/billing/subscription'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
}
export const getSubscription = async ({ workspaceId, user }: Props) => {
if (!env.STRIPE_SECRET_KEY)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Stripe environment variables are missing',
})
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
stripeId: true,
members: {
select: {
userId: true,
},
},
},
})
if (!workspace || isReadWorkspaceFobidden(workspace, user))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
if (!workspace?.stripeId)
return {
subscription: null,
}
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const subscriptions = await stripe.subscriptions.list({
customer: workspace.stripeId,
})
const currentSubscription = subscriptions.data
.filter((sub) => ['past_due', 'active'].includes(sub.status))
.sort((a, b) => a.created - b.created)
.shift()
if (!currentSubscription)
return {
subscription: null,
}
return {
subscription: {
currentBillingPeriod: subscriptionSchema.shape.currentBillingPeriod.parse(
{
start: new Date(currentSubscription.current_period_start),
end: new Date(currentSubscription.current_period_end),
}
),
status: subscriptionSchema.shape.status.parse(currentSubscription.status),
currency: currentSubscription.currency as 'usd' | 'eur',
cancelDate: currentSubscription.cancel_at
? new Date(currentSubscription.cancel_at * 1000)
: undefined,
},
}
}

View File

@@ -0,0 +1,96 @@
import { TRPCError } from '@trpc/server'
import { env } from '@typebot.io/env'
import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
import Stripe from 'stripe'
import { isReadWorkspaceFobidden } from '@typebot.io/db-rules/isReadWorkspaceFobidden'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
}
export const getUsage = async ({ workspaceId, user }: Props) => {
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
stripeId: true,
plan: true,
members: {
select: {
userId: true,
},
},
typebots: {
select: { id: true },
},
},
})
if (!workspace || isReadWorkspaceFobidden(workspace, user))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
if (
!env.STRIPE_SECRET_KEY ||
!workspace.stripeId ||
(workspace.plan !== 'STARTER' && workspace.plan !== 'PRO')
) {
const now = new Date()
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
const totalChatsUsed = await prisma.result.count({
where: {
typebotId: { in: workspace.typebots.map((typebot) => typebot.id) },
hasStarted: true,
createdAt: {
gte: firstDayOfMonth,
},
},
})
const firstDayOfNextMonth = new Date(
firstDayOfMonth.getFullYear(),
firstDayOfMonth.getMonth() + 1,
1
)
return { totalChatsUsed, resetsAt: firstDayOfNextMonth }
}
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const subscriptions = await stripe.subscriptions.list({
customer: workspace.stripeId,
})
const currentSubscription = subscriptions.data
.filter((sub) => ['past_due', 'active'].includes(sub.status))
.sort((a, b) => a.created - b.created)
.shift()
if (!currentSubscription)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: `No subscription found on workspace: ${workspaceId}`,
})
const totalChatsUsed = await prisma.result.count({
where: {
typebotId: { in: workspace.typebots.map((typebot) => typebot.id) },
hasStarted: true,
createdAt: {
gte: new Date(currentSubscription.current_period_start * 1000),
},
},
})
return {
totalChatsUsed,
resetsAt: new Date(currentSubscription.current_period_end * 1000),
}
}

View File

@@ -0,0 +1,59 @@
import { TRPCError } from '@trpc/server'
import { isAdminWriteWorkspaceForbidden } from '@typebot.io/db-rules/isAdminWriteWorkspaceForbidden'
import { env } from '@typebot.io/env'
import { isDefined } from '@typebot.io/lib'
import prisma from '@typebot.io/lib/prisma'
import { User } from '@typebot.io/prisma'
import { Stripe } from 'stripe'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
}
export const listInvoices = async ({ workspaceId, user }: Props) => {
if (!env.STRIPE_SECRET_KEY)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'STRIPE_SECRET_KEY var is missing',
})
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
stripeId: true,
members: {
select: {
userId: true,
role: true,
},
},
},
})
if (!workspace?.stripeId || isAdminWriteWorkspaceForbidden(workspace, user))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const invoices = await stripe.invoices.list({
customer: workspace.stripeId,
limit: 50,
})
return {
invoices: invoices.data
.filter(
(invoice) => isDefined(invoice.invoice_pdf) && isDefined(invoice.id)
)
.map((invoice) => ({
id: invoice.number as string,
url: invoice.invoice_pdf as string,
amount: invoice.subtotal,
currency: invoice.currency,
date: invoice.status_transitions.paid_at,
})),
}
}

View File

@@ -0,0 +1,153 @@
import { TRPCError } from '@trpc/server'
import { env } from '@typebot.io/env'
import prisma from '@typebot.io/lib/prisma'
import { Plan } from '@typebot.io/prisma'
import Stripe from 'stripe'
import { trackEvents } from '@typebot.io/telemetry/trackEvents'
import { isAdminWriteWorkspaceForbidden } from '@typebot.io/db-rules/isAdminWriteWorkspaceForbidden'
import { User } from '@typebot.io/schemas'
import { createCheckoutSessionUrl } from '../helpers/createCheckoutSessionUrl'
type Props = {
workspaceId: string
user: Pick<User, 'email' | 'id'>
plan: 'STARTER' | 'PRO'
returnUrl: string
currency: 'usd' | 'eur'
}
export const updateSubscription = async ({
workspaceId,
user,
plan,
returnUrl,
currency,
}: Props) => {
if (!env.STRIPE_SECRET_KEY)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Stripe environment variables are missing',
})
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
},
select: {
isPastDue: true,
stripeId: true,
members: {
select: {
userId: true,
role: true,
},
},
},
})
if (workspace?.isPastDue)
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'You have unpaid invoices. Please head over your billing portal to pay it.',
})
if (!workspace?.stripeId || isAdminWriteWorkspaceForbidden(workspace, user))
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const { data } = await stripe.subscriptions.list({
customer: workspace.stripeId,
limit: 1,
status: 'active',
})
const subscription = data[0] as Stripe.Subscription | undefined
const currentPlanItemId = subscription?.items.data.find((item) =>
[env.STRIPE_STARTER_PRICE_ID, env.STRIPE_PRO_PRICE_ID].includes(
item.price.id
)
)?.id
const currentUsageItemId = subscription?.items.data.find(
(item) =>
item.price.id === env.STRIPE_STARTER_CHATS_PRICE_ID ||
item.price.id === env.STRIPE_PRO_CHATS_PRICE_ID
)?.id
const items = [
{
id: currentPlanItemId,
price:
plan === Plan.STARTER
? env.STRIPE_STARTER_PRICE_ID
: env.STRIPE_PRO_PRICE_ID,
quantity: 1,
},
{
id: currentUsageItemId,
price:
plan === Plan.STARTER
? env.STRIPE_STARTER_CHATS_PRICE_ID
: env.STRIPE_PRO_CHATS_PRICE_ID,
},
]
if (subscription) {
if (plan === 'STARTER') {
const totalChatsUsed = await prisma.result.count({
where: {
typebot: { workspaceId },
hasStarted: true,
createdAt: {
gte: new Date(subscription.current_period_start * 1000),
},
},
})
if (totalChatsUsed >= 4000) {
throw new TRPCError({
code: 'BAD_REQUEST',
message:
"You have collected more than 4000 chats during this billing cycle. You can't downgrade to the Starter.",
})
}
}
await stripe.subscriptions.update(subscription.id, {
items,
proration_behavior: 'always_invoice',
})
} else {
const checkoutUrl = await createCheckoutSessionUrl(stripe)({
customerId: workspace.stripeId,
userId: user.id,
workspaceId,
currency,
plan,
returnUrl,
})
return { checkoutUrl }
}
const updatedWorkspace = await prisma.workspace.update({
where: { id: workspaceId },
data: {
plan,
isQuarantined: false,
},
})
await trackEvents([
{
name: 'Subscription updated',
workspaceId,
userId: user.id,
data: {
plan,
},
},
])
return { workspace: updatedWorkspace }
}

View File

@@ -0,0 +1,336 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { Stripe } from 'stripe'
import { buffer } from 'micro'
import { env } from '@typebot.io/env'
import { Plan, WorkspaceRole } from '@typebot.io/prisma'
import prisma from '@typebot.io/lib/prisma'
import { trackEvents } from '@typebot.io/telemetry/trackEvents'
import { prices } from '../constants'
import { Settings } from '@typebot.io/schemas'
import { methodNotAllowed } from '@typebot.io/lib/api/utils'
if (!env.STRIPE_SECRET_KEY || !env.STRIPE_WEBHOOK_SECRET)
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const webhookSecret = env.STRIPE_WEBHOOK_SECRET as string
export const webhookHandler = async (
req: NextApiRequest,
res: NextApiResponse
) => {
if (req.method === 'POST') {
const buf = await buffer(req)
const sig = req.headers['stripe-signature']
if (!sig) return res.status(400).send(`stripe-signature is missing`)
try {
const event = stripe.webhooks.constructEvent(
buf.toString(),
sig.toString(),
webhookSecret
)
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
const metadata = session.metadata as unknown as
| {
plan: 'STARTER' | 'PRO'
workspaceId: string
userId: string
}
| { claimableCustomPlanId: string; userId: string }
if ('plan' in metadata) {
const { workspaceId, plan } = metadata
if (!workspaceId || !plan)
return res
.status(500)
.send({ message: `Couldn't retrieve valid metadata` })
const workspace = await prisma.workspace.update({
where: { id: workspaceId },
data: {
plan,
stripeId: session.customer as string,
isQuarantined: false,
},
include: {
members: {
select: { userId: true },
where: {
role: WorkspaceRole.ADMIN,
},
},
},
})
await trackEvents(
workspace.members.map((m) => ({
name: 'Subscription updated',
workspaceId,
userId: m.userId,
data: {
plan,
},
}))
)
} else {
const { claimableCustomPlanId, userId } = metadata
if (!claimableCustomPlanId)
return res
.status(500)
.send({ message: `Couldn't retrieve valid metadata` })
const { workspaceId, chatsLimit, seatsLimit, storageLimit } =
await prisma.claimableCustomPlan.update({
where: { id: claimableCustomPlanId },
data: { claimedAt: new Date() },
})
await prisma.workspace.updateMany({
where: { id: workspaceId },
data: {
plan: Plan.CUSTOM,
stripeId: session.customer as string,
customChatsLimit: chatsLimit,
customStorageLimit: storageLimit,
customSeatsLimit: seatsLimit,
},
})
await trackEvents([
{
name: 'Subscription updated',
workspaceId,
userId,
data: {
plan: Plan.CUSTOM,
},
},
])
}
return res.status(200).send({ message: 'workspace upgraded in DB' })
}
case 'customer.subscription.updated': {
const subscription = event.data.object as Stripe.Subscription
if (subscription.status !== 'past_due')
return res.send({ message: 'Not past_due, skipping.' })
const existingWorkspace = await prisma.workspace.findFirst({
where: {
stripeId: subscription.customer as string,
},
select: {
isPastDue: true,
id: true,
members: {
select: { userId: true, role: true },
where: { role: WorkspaceRole.ADMIN },
},
},
})
if (!existingWorkspace) throw new Error('Workspace not found')
if (existingWorkspace?.isPastDue)
return res.send({
message: 'Workspace already past due, skipping.',
})
await prisma.workspace.updateMany({
where: {
id: existingWorkspace.id,
},
data: {
isPastDue: true,
},
})
await trackEvents(
existingWorkspace.members.map((m) => ({
name: 'Workspace past due',
workspaceId: existingWorkspace.id,
userId: m.userId,
}))
)
return res.send({ message: 'Workspace set to past due.' })
}
case 'invoice.paid': {
const invoice = event.data.object as Stripe.Invoice
const workspace = await prisma.workspace.findFirst({
where: {
stripeId: invoice.customer as string,
},
select: {
isPastDue: true,
},
})
if (!workspace?.isPastDue)
return res.send({ message: 'Workspace not past_due, skipping.' })
const outstandingInvoices = await stripe.invoices.list({
customer: invoice.customer as string,
status: 'open',
})
const outstandingInvoicesWithAdditionalUsageCosts =
outstandingInvoices.data.filter(
(invoice) => invoice.amount_due > prices['PRO'] * 100
)
if (outstandingInvoicesWithAdditionalUsageCosts.length > 0)
return res.send({
message: 'Workspace has outstanding invoices, skipping.',
})
const updatedWorkspace = await prisma.workspace.update({
where: {
stripeId: invoice.customer as string,
},
data: {
isPastDue: false,
},
select: {
id: true,
members: {
select: { userId: true },
where: {
role: WorkspaceRole.ADMIN,
},
},
},
})
await trackEvents(
updatedWorkspace.members.map((m) => ({
name: 'Workspace past due status removed',
workspaceId: updatedWorkspace.id,
userId: m.userId,
}))
)
return res.send({ message: 'Workspace was regulated' })
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription
const { data } = await stripe.subscriptions.list({
customer: subscription.customer as string,
limit: 1,
status: 'active',
})
const existingSubscription = data[0] as
| Stripe.Subscription
| undefined
if (existingSubscription)
return res.send({
message:
'An active subscription still exists. Skipping downgrade.',
})
const outstandingInvoices = await stripe.invoices.list({
customer: subscription.customer as string,
status: 'open',
})
const outstandingInvoicesWithAdditionalUsageCosts =
outstandingInvoices.data.filter(
(invoice) => invoice.amount_due > prices['PRO'] * 100
)
const workspaceExist =
(await prisma.workspace.count({
where: {
stripeId: subscription.customer as string,
},
})) > 0
if (!workspaceExist)
return res.send({ message: 'Workspace not found, skipping...' })
const workspace = await prisma.workspace.update({
where: {
stripeId: subscription.customer as string,
},
data: {
plan: Plan.FREE,
customChatsLimit: null,
customStorageLimit: null,
customSeatsLimit: null,
isPastDue: outstandingInvoicesWithAdditionalUsageCosts.length > 0,
},
include: {
members: {
select: { userId: true },
where: {
role: WorkspaceRole.ADMIN,
},
},
},
})
await trackEvents(
workspace.members.map((m) => ({
name: 'Subscription updated',
workspaceId: workspace.id,
userId: m.userId,
data: {
plan: Plan.FREE,
},
}))
)
const typebots = await prisma.typebot.findMany({
where: {
workspaceId: workspace.id,
isArchived: { not: true },
},
include: { publishedTypebot: true },
})
for (const typebot of typebots) {
const settings = typebot.settings as Settings
if (settings.general?.isBrandingEnabled) continue
await prisma.typebot.updateMany({
where: { id: typebot.id },
data: {
settings: {
...settings,
general: {
...settings.general,
isBrandingEnabled: true,
},
whatsApp: settings.whatsApp
? {
...settings.whatsApp,
isEnabled: false,
}
: undefined,
},
},
})
const publishedTypebotSettings = typebot.publishedTypebot
?.settings as Settings | null
if (
!publishedTypebotSettings ||
publishedTypebotSettings?.general?.isBrandingEnabled
)
continue
await prisma.publicTypebot.updateMany({
where: { id: typebot.id },
data: {
settings: {
...publishedTypebotSettings,
general: {
...publishedTypebotSettings.general,
isBrandingEnabled: true,
},
},
},
})
}
return res.send({ message: 'workspace downgraded in DB' })
}
default: {
return res.status(304).send({ message: 'event not handled' })
}
}
} catch (err) {
console.error(err)
if (err instanceof Error) {
console.error(err)
return res.status(400).send(`Webhook Error: ${err.message}`)
}
return res.status(500).send(`Error occured: ${err}`)
}
}
return methodNotAllowed(res)
}