Files
bot/apps/builder/src/features/billing/api/updateSubscription.ts

171 lines
5.0 KiB
TypeScript
Raw Normal View History

import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import prisma from '@/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { Plan, WorkspaceRole } from '@typebot.io/prisma'
import { workspaceSchema } from '@typebot.io/schemas'
import Stripe from 'stripe'
import { isDefined } from '@typebot.io/lib'
import { z } from 'zod'
import {
getChatsLimit,
getStorageLimit,
priceIds,
} from '@typebot.io/lib/pricing'
import { chatPriceIds, storagePriceIds } from './getSubscription'
export const updateSubscription = authenticatedProcedure
.meta({
openapi: {
method: 'PATCH',
path: '/billing/subscription',
protect: true,
summary: 'Update subscription',
tags: ['Billing'],
},
})
.input(
z.object({
workspaceId: z.string(),
plan: z.enum([Plan.STARTER, Plan.PRO]),
additionalChats: z.number(),
additionalStorage: z.number(),
currency: z.enum(['usd', 'eur']),
isYearly: z.boolean(),
})
)
.output(
z.object({
workspace: workspaceSchema,
})
)
.mutation(
async ({
input: {
workspaceId,
plan,
additionalChats,
additionalStorage,
currency,
isYearly,
},
ctx: { user },
}) => {
if (!process.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,
members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } },
},
})
if (!workspace?.stripeId)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Workspace not found',
})
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
})
const { data } = await stripe.subscriptions.list({
customer: workspace.stripeId,
})
const subscription = data[0] as Stripe.Subscription | undefined
const currentPlanItemId = subscription?.items.data.find((item) =>
[
process.env.STRIPE_STARTER_PRODUCT_ID,
process.env.STRIPE_PRO_PRODUCT_ID,
].includes(item.price.product.toString())
)?.id
const currentAdditionalChatsItemId = subscription?.items.data.find(
(item) => chatPriceIds.includes(item.price.id)
)?.id
const currentAdditionalStorageItemId = subscription?.items.data.find(
(item) => storagePriceIds.includes(item.price.id)
)?.id
const frequency = isYearly ? 'yearly' : 'monthly'
const items = [
{
id: currentPlanItemId,
price: priceIds[plan].base[frequency],
quantity: 1,
},
additionalChats === 0 && !currentAdditionalChatsItemId
? undefined
: {
id: currentAdditionalChatsItemId,
price: priceIds[plan].chats[frequency],
quantity: getChatsLimit({
plan,
additionalChatsIndex: additionalChats,
customChatsLimit: null,
}),
deleted: subscription ? additionalChats === 0 : undefined,
},
additionalStorage === 0 && !currentAdditionalStorageItemId
? undefined
: {
id: currentAdditionalStorageItemId,
price: priceIds[plan].storage[frequency],
quantity: getStorageLimit({
plan,
additionalStorageIndex: additionalStorage,
customStorageLimit: null,
}),
deleted: subscription ? additionalStorage === 0 : undefined,
},
].filter(isDefined)
if (subscription) {
await stripe.subscriptions.update(subscription.id, {
items,
})
} else {
const { data: paymentMethods } = await stripe.paymentMethods.list({
customer: workspace.stripeId,
})
if (paymentMethods.length === 0) {
throw Error('No payment method found')
}
await stripe.subscriptions.create({
customer: workspace.stripeId,
items,
currency,
default_payment_method: paymentMethods[0].id,
automatic_tax: {
enabled: true,
},
})
}
const updatedWorkspace = await prisma.workspace.update({
where: { id: workspaceId },
data: {
plan,
additionalChatsIndex: additionalChats,
additionalStorageIndex: additionalStorage,
isQuarantined: false,
},
})
await sendTelemetryEvents([
{
name: 'Subscription updated',
workspaceId,
userId: user.id,
data: {
plan,
additionalChatsIndex: additionalChats,
additionalStorageIndex: additionalStorage,
},
},
])
return { workspace: updatedWorkspace }
}
)